/*
 * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
 * http://www.gnu.org/licenses/gpl-3.0.html
 *
 * $Revision: 11210 $
 * $Id: projectmanagerui.cpp 11210 2017-10-18 23:42:42Z fuscated $
 * $HeadURL: http://svn.code.sf.net/p/codeblocks/code/branches/release-17.xx/src/src/projectmanagerui.cpp $
 */

/*项目管理器界面*/

#include "sdk.h"
#include "projectmanagerui.h"

#ifndef CB_PRECOMP
    #include <algorithm>

    #include <wx/checkbox.h>
    #include <wx/choicdlg.h>
    #include <wx/dir.h>
    #include <wx/filedlg.h>
    #include <wx/imaglist.h>
    #include <wx/listctrl.h>
    #include <wx/menu.h>
    #include <wx/settings.h>
    #include <wx/textdlg.h>
    #include <wx/xrc/xmlres.h>

    #include "cbeditor.h"
    #include "cbproject.h"
    #include "cbworkspace.h"
    #include "configmanager.h"
    #include "editormanager.h"
    #include "logmanager.h"
#endif

#include <unordered_map>

#include "cbauibook.h"
#include "cbcolourmanager.h"
#include "confirmreplacedlg.h"
#include "filefilters.h"
#include "filegroupsandmasks.h"
#include "multiselectdlg.h"
#include "projectdepsdlg.h"
#include "projectfileoptionsdlg.h"
#include "projectoptionsdlg.h"
#include "projectsfilemasksdlg.h"

#include "goto_file.h"
#include "startherepage.h"

namespace
{

/*"打开方式"上下文菜单中的最大项目数[20],保持与下面的数组同步!*/
static const unsigned int MAX_OPEN_WITH_ITEMS = 20;
static const int idOpenWith[] =
{
    static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()),
    static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()),
    static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()),
    static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()), static_cast<int>(wxNewId()),
};

/*特殊条目:使用内部编辑器强制打开*/
static const int idOpenWithInternal = wxNewId();

const int ID_ProjectManager              = wxNewId();
const int idMenuSetActiveProject         = wxNewId();
const int idMenuOpenFile                 = wxNewId();
const int idMenuSaveProject              = wxNewId();
const int idMenuSaveFile                 = wxNewId();
const int idMenuCloseProject             = wxNewId();
const int idMenuCloseFile                = wxNewId();
const int idMenuAddFilePopup             = wxNewId();
const int idMenuAddFilesRecursivelyPopup = wxNewId();
const int idMenuAddFile                  = wxNewId();
const int idMenuAddFilesRecursively      = wxNewId();
const int idMenuRemoveFolderFilesPopup   = wxNewId();
const int idMenuOpenFolderFilesPopup     = wxNewId();
const int idMenuRemoveFilePopup          = wxNewId();
const int idMenuRemoveFile               = wxNewId();
const int idMenuRenameFile               = wxNewId();
const int idMenuRenameVFolder            = wxNewId();
const int idMenuProjectNotes             = wxNewId();
const int idMenuProjectProperties        = wxNewId();
const int idMenuFileProperties           = wxNewId();
const int idMenuTreeProjectProperties    = wxNewId();
const int idMenuTreeFileProperties       = wxNewId();
const int idMenuTreeOptionsCompile       = wxNewId();
const int idMenuTreeOptionsLink          = wxNewId();
const int idMenuTreeOptionsDisableBoth   = wxNewId();
const int idMenuTreeOptionsEnableBoth    = wxNewId();
const int idMenuGotoFile                 = wxNewId();
const int idMenuExecParams               = wxNewId();
const int idMenuViewCategorize           = wxNewId();
const int idMenuViewUseFolders           = wxNewId();
const int idMenuViewHideFolderName       = wxNewId();
const int idMenuViewFileMasks            = wxNewId();
const int idMenuNextProject              = wxNewId();
const int idMenuPriorProject             = wxNewId();
const int idMenuProjectTreeProps         = wxNewId();
const int idMenuProjectUp                = wxNewId();
const int idMenuProjectDown              = wxNewId();
const int idMenuViewCategorizePopup      = wxNewId();
const int idMenuViewUseFoldersPopup      = wxNewId();
const int idMenuViewHideFolderNamePopup  = wxNewId();
const int idMenuTreeRenameWorkspace      = wxNewId();
const int idMenuTreeSaveWorkspace        = wxNewId();
const int idMenuTreeSaveAsWorkspace      = wxNewId();
const int idMenuTreeCloseWorkspace       = wxNewId();
const int idMenuAddVirtualFolder         = wxNewId();
const int idMenuDeleteVirtualFolder      = wxNewId();
const int idMenuFindFile = wxNewId();
const int idNB           = wxNewId();
const int idNB_TabTop    = wxNewId();
const int idNB_TabBottom = wxNewId();
} // anonymous namespace

namespace
{
bool ProjectCanDragNode(cbProject* project, wxTreeCtrl* tree, wxTreeItemId node);
bool ProjectNodeDragged(cbProject* project, wxTreeCtrl* tree, wxArrayTreeItemIds& fromArray, wxTreeItemId to);
bool ProjectVirtualFolderAdded(cbProject* project, wxTreeCtrl* tree, wxTreeItemId parent_node,const wxString& virtual_folder);
void ProjectVirtualFolderDeleted(cbProject* project, wxTreeCtrl* tree, wxTreeItemId node);
bool ProjectVirtualFolderRenamed(cbProject* project, wxTreeCtrl* tree, wxTreeItemId node, const wxString& new_name);
bool ProjectVirtualFolderDragged(cbProject* project, wxTreeCtrl* tree, wxTreeItemId from, wxTreeItemId to);
bool ProjectShowOptions(cbProject* project);
} // anonymous namespace


BEGIN_EVENT_TABLE(ProjectManagerUI, wxEvtHandler)
    EVT_TREE_BEGIN_DRAG(ID_ProjectManager,       ProjectManagerUI::OnTreeBeginDrag)
    EVT_TREE_END_DRAG(ID_ProjectManager,         ProjectManagerUI::OnTreeEndDrag)

    EVT_TREE_BEGIN_LABEL_EDIT(ID_ProjectManager, ProjectManagerUI::OnBeginEditNode)
    EVT_TREE_END_LABEL_EDIT(ID_ProjectManager,   ProjectManagerUI::OnEndEditNode)

    EVT_TREE_ITEM_ACTIVATED(ID_ProjectManager,   ProjectManagerUI::OnProjectFileActivated)
    EVT_TREE_ITEM_RIGHT_CLICK(ID_ProjectManager, ProjectManagerUI::OnTreeItemRightClick)
    EVT_TREE_KEY_DOWN(ID_ProjectManager,         ProjectManagerUI::OnKeyDown)
    EVT_COMMAND_RIGHT_CLICK(ID_ProjectManager,   ProjectManagerUI::OnRightClick)

    EVT_AUINOTEBOOK_TAB_RIGHT_UP(idNB, ProjectManagerUI::OnTabContextMenu)

    EVT_MENU_RANGE(idOpenWith[0], idOpenWith[MAX_OPEN_WITH_ITEMS - 1], ProjectManagerUI::OnOpenWith)
    EVT_MENU(idOpenWithInternal,             ProjectManagerUI::OnOpenWith)
    EVT_MENU(idNB_TabTop,                    ProjectManagerUI::OnTabPosition)
    EVT_MENU(idNB_TabBottom,                 ProjectManagerUI::OnTabPosition)
    EVT_MENU(idMenuSetActiveProject,         ProjectManagerUI::OnSetActiveProject)
    EVT_MENU(idMenuNextProject,              ProjectManagerUI::OnSetActiveProject)
    EVT_MENU(idMenuPriorProject,             ProjectManagerUI::OnSetActiveProject)
    EVT_MENU(idMenuProjectUp,                ProjectManagerUI::OnSetActiveProject)
    EVT_MENU(idMenuProjectDown,              ProjectManagerUI::OnSetActiveProject)
    EVT_MENU(idMenuTreeRenameWorkspace,      ProjectManagerUI::OnRenameWorkspace)
    EVT_MENU(idMenuTreeSaveWorkspace,        ProjectManagerUI::OnSaveWorkspace)
    EVT_MENU(idMenuTreeSaveAsWorkspace,      ProjectManagerUI::OnSaveAsWorkspace)
    EVT_MENU(idMenuTreeCloseWorkspace,       ProjectManagerUI::OnCloseWorkspace)
    EVT_MENU(idMenuAddVirtualFolder,         ProjectManagerUI::OnAddVirtualFolder)
    EVT_MENU(idMenuDeleteVirtualFolder,      ProjectManagerUI::OnDeleteVirtualFolder)
    EVT_MENU(idMenuAddFile,                  ProjectManagerUI::OnAddFileToProject)
    EVT_MENU(idMenuAddFilesRecursively,      ProjectManagerUI::OnAddFilesToProjectRecursively)
    EVT_MENU(idMenuRemoveFile,               ProjectManagerUI::OnRemoveFileFromProject)
    EVT_MENU(idMenuAddFilePopup,             ProjectManagerUI::OnAddFileToProject)
    EVT_MENU(idMenuAddFilesRecursivelyPopup, ProjectManagerUI::OnAddFilesToProjectRecursively)
    EVT_MENU(idMenuRemoveFolderFilesPopup,   ProjectManagerUI::OnRemoveFileFromProject)
    EVT_MENU(idMenuOpenFolderFilesPopup,     ProjectManagerUI::OnOpenFolderFiles)
    EVT_MENU(idMenuRemoveFilePopup,          ProjectManagerUI::OnRemoveFileFromProject)
    EVT_MENU(idMenuRenameFile,               ProjectManagerUI::OnRenameFile)
    EVT_MENU(idMenuRenameVFolder,            ProjectManagerUI::OnRenameVirtualFolder)
    EVT_MENU(idMenuSaveProject,              ProjectManagerUI::OnSaveProject)
    EVT_MENU(idMenuSaveFile,                 ProjectManagerUI::OnSaveFile)
    EVT_MENU(idMenuCloseProject,             ProjectManagerUI::OnCloseProject)
    EVT_MENU(idMenuCloseFile,                ProjectManagerUI::OnCloseFile)
    EVT_MENU(idMenuOpenFile,                 ProjectManagerUI::OnOpenFile)
    EVT_MENU(idMenuProjectNotes,             ProjectManagerUI::OnNotes)
    EVT_MENU(idMenuProjectProperties,        ProjectManagerUI::OnProperties)
    EVT_MENU(idMenuFileProperties,           ProjectManagerUI::OnProperties)
    EVT_MENU(idMenuTreeOptionsCompile,       ProjectManagerUI::OnFileOptions)
    EVT_MENU(idMenuTreeOptionsLink,          ProjectManagerUI::OnFileOptions)
    EVT_MENU(idMenuTreeOptionsEnableBoth,    ProjectManagerUI::OnFileOptions)
    EVT_MENU(idMenuTreeOptionsDisableBoth,   ProjectManagerUI::OnFileOptions)
    EVT_MENU(idMenuTreeProjectProperties,    ProjectManagerUI::OnProperties)
    EVT_MENU(idMenuTreeFileProperties,       ProjectManagerUI::OnProperties)
    EVT_MENU(idMenuGotoFile,                 ProjectManagerUI::OnGotoFile)
    EVT_MENU(idMenuExecParams,               ProjectManagerUI::OnExecParameters)
    EVT_MENU(idMenuViewCategorize,           ProjectManagerUI::OnViewCategorize)
    EVT_MENU(idMenuViewUseFolders,           ProjectManagerUI::OnViewUseFolders)
    EVT_MENU(idMenuViewHideFolderName,       ProjectManagerUI::OnViewHideFolderName)
    EVT_MENU(idMenuViewCategorizePopup,      ProjectManagerUI::OnViewCategorize)
    EVT_MENU(idMenuViewUseFoldersPopup,      ProjectManagerUI::OnViewUseFolders)
    EVT_MENU(idMenuViewHideFolderNamePopup,  ProjectManagerUI::OnViewHideFolderName)
    EVT_MENU(idMenuViewFileMasks,            ProjectManagerUI::OnViewFileMasks)
    EVT_MENU(idMenuFindFile,                 ProjectManagerUI::OnFindFile)
    EVT_IDLE(                                ProjectManagerUI::OnIdle)

    EVT_UPDATE_UI(idMenuFileProperties,      ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuProjectProperties,   ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuAddFile,             ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuAddFilesRecursively, ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuRemoveFile,          ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuProjectTreeProps,    ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuAddVirtualFolder,    ProjectManagerUI::OnUpdateUI)
    EVT_UPDATE_UI(idMenuDeleteVirtualFolder, ProjectManagerUI::OnUpdateUI)

END_EVENT_TABLE()

ProjectManagerUI::ProjectManagerUI() :
    m_pTree(nullptr),
    m_TreeFreezeCounter(0),
    m_isCheckingForExternallyModifiedProjects(false)
{
    m_pNotebook = new cbAuiNotebook(Manager::Get()->GetAppWindow(), idNB,wxDefaultPosition, wxDefaultSize, wxAUI_NB_WINDOWLIST_BUTTON);
    if (Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/project_tabs_bottom"), false))
        m_pNotebook->SetWindowStyleFlag(m_pNotebook->GetWindowStyleFlag() | wxAUI_NB_BOTTOM);

    InitPane();

    ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("project_manager"));
    m_TreeVisualState  = ptvsNone;
    m_TreeVisualState |= (cfg->ReadBool(_T("/categorize_tree"),  true)  ? ptvsCategorize     : ptvsNone);
    m_TreeVisualState |= (cfg->ReadBool(_T("/use_folders"),      true)  ? ptvsUseFolders     : ptvsNone);
    m_TreeVisualState |= (cfg->ReadBool(_T("/hide_folder_name"), false) ? ptvsHideFolderName : ptvsNone);
    /*修复无效组合,"使用文件夹"具有优先权*/
    if ( (m_TreeVisualState&ptvsUseFolders) && (m_TreeVisualState&ptvsHideFolderName) )
    {
        m_TreeVisualState &= ~ptvsHideFolderName;
        cfg->Write(_T("/hide_folder_name"), false);
    }

    RebuildTree();

    Manager::Get()->GetColourManager()->RegisterColour(wxT("项目树"), wxT("未编译的文件 (头文件/资源文件)"),
                                                       wxT("project_tree_non_source_files"),
                                                       wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));

    /*事件处理.这肯定是启动时最后激活的东西.构造函数和析构函数必须始终遵循后进先出(LIFO)规则: 后进先出(Last in, first out).*/

    Manager::Get()->GetAppWindow()->PushEventHandler(this);
}

ProjectManagerUI::~ProjectManagerUI()
{
    delete m_pImages;
    m_pImages = nullptr;
    m_pNotebook->Destroy();
}

/*初始化面板*/
void ProjectManagerUI::InitPane() {
    if (Manager::IsAppShuttingDown()) { return; }
    if (m_pTree) { return; }
    BuildTree();
    m_pNotebook->AddPage(m_pTree, wxT("项目"));
}

/*创建项目树*/
void ProjectManagerUI::BuildTree()
{
    m_pTree = new cbTreeCtrl(m_pNotebook, ID_ProjectManager);

    static const wxString imgs[] =
    {

        /*注:在(全局变量)globals.h中与(文件可视状态)FileVisualState保持同步!以下与(可编辑,源)文件状态相关*/
        _T("file.png"),                  // fvsNormal
        _T("file-missing.png"),          // fvsMissing,
        _T("file-modified.png"),         // fvsModified,
        _T("file-readonly.png"),         // fvsReadOnly,

        /*以下与版本控制系统(vc)相关*/
        _T("rc-file-added.png"),         // fvsVcAdded,
        _T("rc-file-conflict.png"),      // fvsVcConflict,
        _T("rc-file-missing.png"),       // fvsVcMissing,
        _T("rc-file-modified.png"),      // fvsVcModified,
        _T("rc-file-outofdate.png"),     // fvsVcOutOfDate,
        _T("rc-file-uptodate.png"),      // fvsVcUpToDate,
        _T("rc-file-requireslock.png"),  // fvsVcRequiresLock,
        _T("rc-file-external.png"),      // fvsVcExternal,
        _T("rc-file-gotlock.png"),       // fvsVcGotLock,
        _T("rc-file-lockstolen.png"),    // fvsVcLockStolen,
        _T("rc-file-mismatch.png"),      // fvsVcMismatch,
        _T("rc-file-noncontrolled.png"), // fvsVcNonControlled,

        /*以下内容与C::B workspace/project/folder/virtual相关*/
        _T("workspace.png"),             // fvsWorkspace,         WorkspaceIconIndex()
        _T("workspace-readonly.png"),    // fvsWorkspaceReadOnly, WorkspaceIconIndex(true)
        _T("project.png"),               // fvsProject,           ProjectIconIndex()
        _T("project-readonly.png"),      // fvsProjectReadOnly,   ProjectIconIndex(true)
        _T("folder_open.png"),           // fvsFolder,            FolderIconIndex()
        _T("vfolder_open.png"),          // fvsVirtualFolder,     VirtualFolderIconIndex()

        wxEmptyString
    };
    wxBitmap bmp;
    m_pImages = new wxImageList(16, 16);
    wxString prefix = ConfigManager::ReadDataPath() + _T("/images/");

    int i = 0;
    while (!imgs[i].IsEmpty())
    {
        bmp = cbLoadBitmap(prefix + imgs[i], wxBITMAP_TYPE_PNG); // workspace
        m_pImages->Add(bmp);
        ++i;
    }
    m_pTree->SetImageList(m_pImages);
}

/*重建项目树*/
void ProjectManagerUI::RebuildTree() {
    /*在启动大型项目时节省大量时间*/
    if (Manager::IsAppShuttingDown()) { return; }

    FreezeTree();
    ProjectManager *pm = Manager::Get()->GetProjectManager();
    ProjectsArray *pa = pm->GetProjects();
    int count = pa->GetCount();
    for (int i = 0; i < count; ++i) {
        if (cbProject *prj = pa->Item(i)) { prj->SaveTreeState(m_pTree); }
    }
    m_pTree->DeleteAllItems();
    wxString title;
    bool read_only = false;
    cbWorkspace *wkspc = pm->GetWorkspace();
    if (wkspc) {
        title = wkspc->GetTitle();
        wxString ws_file = wkspc->GetFilename();
        read_only = (!ws_file.IsEmpty() && wxFile::Exists(ws_file.c_str()) && !wxFile::Access(ws_file.c_str(), wxFile::write));
    }
    if (title.IsEmpty()) { title = wxT("工作区"); }
    m_TreeRoot = m_pTree->AddRoot(title, cbProjectTreeImages::WorkspaceIconIndex(read_only), cbProjectTreeImages::WorkspaceIconIndex(read_only));
    for (int i = 0; i < count; ++i) {
        if (cbProject *prj = pa->Item(i)) {
            BuildProjectTree(prj, m_pTree, m_TreeRoot, m_TreeVisualState, pm->GetFilesGroupsAndMasks());
            m_pTree->SetItemBold(prj->GetProjectNode(), prj == pm->GetActiveProject());
        }
    }
    m_pTree->Expand(m_TreeRoot);

    for (int i = 0; i < count; ++i) {
        if (cbProject *prj = pa->Item(i)) { prj->RestoreTreeState(m_pTree); }
    }
    UnfreezeTree();
}

/*冻结项目树*/
void ProjectManagerUI::FreezeTree() {
    if (!m_pTree) { return; }
    ++m_TreeFreezeCounter;
    m_pTree->Freeze();
}

/*解冻项目树*/
void ProjectManagerUI::UnfreezeTree(cb_unused bool force)
{
    if (!m_pTree) { return; }
    if (m_TreeFreezeCounter)
    {
        --m_TreeFreezeCounter;
        m_pTree->Thaw();
    }
}

/*更新激活项目*/
void ProjectManagerUI::UpdateActiveProject(cbProject* oldProject, cbProject* newProject, bool refresh)
{
    if (oldProject){m_pTree->SetItemBold(oldProject->GetProjectNode(), false);}
    if (newProject)
    {
        wxTreeItemId tid = newProject->GetProjectNode();
        if (tid){m_pTree->SetItemBold(newProject->GetProjectNode(), true);}
    }

    if (refresh){RebuildTree();}
    if (newProject){m_pTree->EnsureVisible(newProject->GetProjectNode());}

    m_pTree->Refresh();
}

void ProjectManagerUI::RemoveProject(cbProject* project)
{
    m_pTree->Delete(project->GetProjectNode());
}

/*获取项目树选定项*/
wxTreeItemId ProjectManagerUI::GetTreeSelection()
{
    /*用户可能选择了多个项目并右键单击了一个项目,因此在这种情况下返回右键单击的项目.*/
    if (m_RightClickItem.IsOk()){return m_RightClickItem;}

    wxArrayTreeItemIds selections;
    unsigned int sel = m_pTree->GetSelections(selections);
    /*通常返回选择列表中的第一项.*/
    if (sel){return selections[0];}

    return wxTreeItemId();
}

/*开始装载工作区*/
void ProjectManagerUI::BeginLoadingWorkspace()
{
    FreezeTree();
    m_pTree->AppendItem(m_pTree->GetRootItem(), wxT("载入工作区..."));
    m_pTree->Expand(m_pTree->GetRootItem());
    UnfreezeTree();
}

/*关闭工作区*/
void ProjectManagerUI::CloseWorkspace()
{
    if (m_pTree)
    {
        m_pTree->SetItemText(m_TreeRoot, wxT("工作区"));
        /*如果需要,更新工作区图标*/
        if (!Manager::IsAppShuttingDown()){RebuildTree();}
    }
}

/*完成载入项目[项目对象|新附加|文件组和掩码]*/
void ProjectManagerUI::FinishLoadingProject(cbProject* project, bool newAddition, cb_unused FilesGroupsAndMasks* fgam) {
    if (newAddition) {
        ProjectManager *pm = Manager::Get()->GetProjectManager();
        BuildProjectTree(project, m_pTree, m_TreeRoot, m_TreeVisualState, pm->GetFilesGroupsAndMasks());
    } else { RebuildTree(); }
    m_pTree->Expand(project->GetProjectNode());
    /*确保根节点已打开*/
    m_pTree->Expand(m_TreeRoot);
}

/*完成载入工作区[激活项目对象|工作区标题]*/
void ProjectManagerUI::FinishLoadingWorkspace(cbProject* activeProject, const wxString &workspaceTitle)
{
    RebuildTree();
    if (activeProject){m_pTree->Expand(activeProject->GetProjectNode());}
    /*确保根节点已打开*/
    m_pTree->Expand(m_TreeRoot);
    m_pTree->SetItemText(m_TreeRoot, workspaceTitle);

    UnfreezeTree(true);
}

/*切换到项目页面*/
void ProjectManagerUI::SwitchToProjectsPage()
{
    CodeBlocksDockEvent showEvent(cbEVT_SHOW_DOCK_WINDOW);
    showEvent.pWindow = m_pNotebook;
    Manager::Get()->ProcessEvent(showEvent);

    int page = m_pNotebook->GetPageIndex(m_pTree);
    if (page != wxNOT_FOUND){m_pNotebook->SetSelection(page);}
}

/*在项目树中显示项目文件*/
void ProjectManagerUI::ShowFileInTree(ProjectFile &projectFile)
{
    /*首先取消选择上一个所选项目(如果有),因为wxTR_MULTIPLE标志而需要*/
    m_pTree->UnselectAll();

    const wxTreeItemId &itemId = projectFile.GetTreeItemId();
    if (itemId.IsOk())
    {
        m_pTree->EnsureVisible(itemId);
        m_pTree->SelectItem(itemId, true);
    }
}

/*创建菜单*/
void ProjectManagerUI::CreateMenu(wxMenuBar* menuBar)
{
    /*TODO(mandrav#1#): 将菜单项从main.cpp移到此处*/

    if (menuBar)
    {

        Manager::Get()->GetLogManager()->DebugLog(wxT("--15.1.7.6.2c5.1搜索菜单..."));
        int pos = menuBar->FindMenu(wxT("搜索"));
        wxMenu* menu = menuBar->GetMenu(pos);
        if (menu){menu->Append(idMenuGotoFile, wxT("转到行...\tAlt-G"));}

        Manager::Get()->GetLogManager()->DebugLog(wxString::FromUTF8("--15.1.7.6.2c5.2文件菜单..."));
        pos = menuBar->FindMenu(wxT("文件"));
        menu = menuBar->GetMenu(pos);
        if (menu)
        {
            menu->Insert(menu->GetMenuItemCount() - 1, idMenuFileProperties, wxT("属性..."));
            /*代替添加分隔符AppendSeparator()*/
            menu->Insert(menu->GetMenuItemCount() - 1, wxID_SEPARATOR, wxT(""));
        }
        Manager::Get()->GetLogManager()->DebugLog(wxString::FromUTF8("--15.1.7.6.2c5.3项目菜单..."));
        pos = menuBar->FindMenu(wxT("项目"));
        menu = menuBar->GetMenu(pos);
        if (menu)
        {
            if (menu->GetMenuItemCount()){menu->AppendSeparator();}
            menu->Append(idMenuAddFile,             wxT("添加文件..."),             wxT("添加文件到项目"));
            menu->Append(idMenuAddFilesRecursively, wxT("递归添加文件..."), wxT("递归添加文件到项目"));
            menu->Append(idMenuRemoveFile,          wxT("移除文件..."),          wxT("从项目中移除文件"));

            menu->AppendSeparator();

            Manager::Get()->GetLogManager()->DebugLog(wxString::FromUTF8("--15.1.7.6.2c5.4创建菜单树..."));
            CreateMenuTreeProps(menu, false);

            menu->Append(idMenuExecParams,        wxT("设置程序参数..."), wxT("设置此项目目标的执行参数"));
            menu->Append(idMenuProjectNotes,      wxT("注释..."));
            menu->Append(idMenuProjectProperties, wxT("属性..."));
        }
    }
}

/*创建菜单树属性弹出菜单*/
void ProjectManagerUI::CreateMenuTreeProps(wxMenu* menu, bool popup)
{
    wxMenu* treeprops = new wxMenu;
    treeprops->Append(idMenuProjectUp,   wxT("上移项目\tCtrl-Shift-Up"),wxT("在项目树中将项目上移"));
    treeprops->Append(idMenuProjectDown, wxT("下移项目\tCtrl-Shift-Down"),wxT("在项目树中下移项目"));
    treeprops->AppendSeparator();
    treeprops->Append(idMenuPriorProject, wxT("激活以前的项目\tAlt-F5"),wxT("在打开的项目列表中激活以前的项目"));
    treeprops->Append(idMenuNextProject,  wxT("激活下一个项目\tAlt-F6"),wxT("在打开的项目列表中激活下一个项目"));
    treeprops->AppendSeparator();
    treeprops->AppendCheckItem((popup ? idMenuViewCategorizePopup     : idMenuViewCategorize),wxT("按文件类型分类"));
    treeprops->AppendCheckItem((popup ? idMenuViewUseFoldersPopup     : idMenuViewUseFolders),wxT("在磁盘上显示文件夹"));
    treeprops->AppendCheckItem((popup ? idMenuViewHideFolderNamePopup : idMenuViewHideFolderName),wxT("隐藏文件夹名称"));
    ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("project_manager"));
    bool do_categorise       = cfg->ReadBool(_T("/categorize_tree"),  true);
    bool do_use_folders      = cfg->ReadBool(_T("/use_folders"),      true);
    /*"使用文件夹"具有优先权*/
    bool do_hide_folder_name = !do_use_folders && cfg->ReadBool(_T("/hide_folder_name"), false);
    /*确保配置一致*/
    cfg->Write(_T("/hide_folder_name"), do_hide_folder_name);
    treeprops->Check((popup ? idMenuViewCategorizePopup     : idMenuViewCategorize),     do_categorise);
    treeprops->Check((popup ? idMenuViewUseFoldersPopup     : idMenuViewUseFolders),     do_use_folders);
    treeprops->Check((popup ? idMenuViewHideFolderNamePopup : idMenuViewHideFolderName), do_hide_folder_name);
    treeprops->Enable((popup ? idMenuViewUseFoldersPopup     : idMenuViewUseFolders),     !do_hide_folder_name);
    treeprops->Enable((popup ? idMenuViewHideFolderNamePopup : idMenuViewHideFolderName), !do_use_folders);
    treeprops->Append(idMenuViewFileMasks, wxT("编辑文件类型和类别..."));
    menu->Append(idMenuProjectTreeProps,wxT("项目树"), treeprops);
}

/*显示菜单*/
void ProjectManagerUI::ShowMenu(wxTreeItemId id, const wxPoint& pt)
{
    if ( !id.IsOk() ){return;}

    wxString caption;
    wxMenu menu;

    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(id);
    bool is_vfolder = ftd && ftd->GetKind() == FileTreeData::ftdkVirtualFolder;
     /*以下代码将检查当前正在编译的项目.如果它发现所选的是项目且项目当前正在编译,则它将禁用某些选项*/
    bool PopUpMenuOption = true;
    ProjectsArray* pa = Manager::Get()->GetProjectManager()->GetProjects();
    if (   pa && ftd
        && (   ftd->GetKind() == FileTreeData::ftdkProject
            || ftd->GetKind() == FileTreeData::ftdkFile
            || ftd->GetKind() == FileTreeData::ftdkFolder ) )
    {
        PopUpMenuOption = !cbHasRunningCompilers(Manager::Get()->GetPluginManager());
    }

    /*如果不是工作区,请添加更多选项*/
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if (ftd)
    {
        /*如果是一个项目*/
        if (ftd->GetKind() == FileTreeData::ftdkProject)
        {
            if (ftd->GetProject() != pm->GetActiveProject())
            {
                menu.Append(idMenuSetActiveProject, wxT("激活项目"));
                menu.Enable(idMenuSetActiveProject, PopUpMenuOption);
            }
            menu.Append(idMenuSaveProject,              wxT("保存项目"));
            menu.Enable(idMenuSaveProject, PopUpMenuOption);
            menu.Append(idMenuCloseProject,             wxT("关闭项目"));
            menu.Enable(idMenuCloseProject, PopUpMenuOption);
            menu.AppendSeparator();
            menu.Append(idMenuAddFilePopup,             wxT("添加文件..."));
            menu.Enable(idMenuAddFilePopup, PopUpMenuOption);
            menu.Append(idMenuAddFilesRecursivelyPopup, wxT("递归添加文件..."));
            menu.Enable(idMenuAddFilesRecursivelyPopup, PopUpMenuOption);
            menu.Append(idMenuRemoveFile,               wxT("移除文件..."));
            menu.AppendSeparator();
            menu.Append(idMenuFindFile,                 wxT("查找文件..."));
            menu.AppendSeparator();
            CreateMenuTreeProps(&menu, true);
            menu.Append(idMenuAddVirtualFolder,         wxT("添加一个新的虚拟目录..."));
            if (is_vfolder) {
                menu.Append(idMenuDeleteVirtualFolder, wxT("删除一个虚拟目录..."));
            }
        }

        /*如果是一个文件*/
        else if (ftd->GetKind() == FileTreeData::ftdkFile)
        {
            /*被选定项目*/
            ProjectFile* pf = ftd->GetProjectFile();

            /*已经在编辑器中打开?*/
            EditorBase* eb = Manager::Get()->GetEditorManager()->IsOpen(pf->file.GetFullPath());
            if (eb)
            {
                /*已经激活?*/
                bool active = Manager::Get()->GetEditorManager()->GetActiveEditor() == eb;

                if (!active)
                {
                    caption.Printf(wxT("切换到 %s"), m_pTree->GetItemText(id).wx_str());
                    menu.Append(idMenuOpenFile, caption);
                }
                caption.Printf(wxT("保存 %s"), m_pTree->GetItemText(id).wx_str());
                menu.Append(idMenuSaveFile, caption);
                caption.Printf(wxT("关闭 %s"), m_pTree->GetItemText(id).wx_str());
                menu.Append(idMenuCloseFile, caption);
            }
            else
            {
                caption.Printf(wxT("打开 %s"), m_pTree->GetItemText(id).wx_str());
                menu.Append(idMenuOpenFile, caption);
            }

            /*添加"打开方式(带参数打开)"菜单*/
            wxMenu* openWith = new wxMenu;
            PluginsArray mimes = Manager::Get()->GetPluginManager()->GetMimeOffers();
            for (unsigned int i = 0; i < mimes.GetCount() && i < MAX_OPEN_WITH_ITEMS; ++i)
            {
                cbMimePlugin* plugin = (cbMimePlugin*)mimes[i];
                if (plugin && plugin->CanHandleFile(m_pTree->GetItemText(id)))
                {
                    const PluginInfo* info = Manager::Get()->GetPluginManager()->GetPluginInfo(plugin);
                    openWith->Append(idOpenWith[i], info ? info->title : wxString(wxT("<未知插件>")));
                }
            }
            openWith->AppendSeparator();
            openWith->Append(idOpenWithInternal, wxT("内部编辑器"));
            menu.Append(wxID_ANY, wxT(" 带参数打开"), openWith);

            if (pf->GetFileState() == fvsNormal &&  !Manager::Get()->GetEditorManager()->IsOpen(pf->file.GetFullPath()))
            {
                menu.AppendSeparator();
                menu.Append(idMenuRenameFile,  wxT("重命名文件..."));
                menu.Enable(idMenuRenameFile, PopUpMenuOption);
            }
            menu.AppendSeparator();
            menu.Append(idMenuRemoveFilePopup, wxT("从项目中移除文件"));
            menu.Enable(idMenuRemoveFilePopup, PopUpMenuOption);
        }else if (ftd->GetKind() == FileTreeData::ftdkFolder){
            /*如果是一个路径*/
            menu.Append(idMenuAddFilePopup, wxT("添加文件..."));
            menu.Enable(idMenuAddFilePopup, PopUpMenuOption);
            menu.Append(idMenuAddFilesRecursivelyPopup, wxT("递归添加文件..."));
            menu.Enable(idMenuAddFilesRecursivelyPopup, PopUpMenuOption);
            menu.AppendSeparator();
            menu.Append(idMenuRemoveFile, wxT("移动文件..."));
            menu.AppendSeparator();
            menu.Append(idMenuFindFile, wxT("查找文件..."));
            menu.AppendSeparator();
            wxFileName f(ftd->GetFolder());
            f.MakeRelativeTo(ftd->GetProject()->GetCommonTopLevelPath());
            menu.Append(idMenuRemoveFolderFilesPopup, wxString::Format(wxT("移除 %s*"), f.GetFullPath().wx_str()));
            menu.Enable(idMenuRemoveFolderFilesPopup, PopUpMenuOption);
            menu.Append(idMenuOpenFolderFilesPopup, wxString::Format(wxT("打开 %s*"), f.GetFullPath().wx_str()));
        }else if (is_vfolder){
            /*如果是一个虚拟目录*/
            menu.Append(idMenuAddVirtualFolder,    wxT("添加一个新的虚拟目录..."));
            menu.Append(idMenuDeleteVirtualFolder, wxT("删除这个新的虚拟目录"));
            menu.Append(idMenuRenameVFolder,       wxT("重命名这个虚拟目录"));
            menu.AppendSeparator();
            menu.Append(idMenuRemoveFile, wxT("移除文件..."));
            menu.Append(idMenuRemoveFolderFilesPopup, wxString::Format(wxT("移除 %s*"), ftd->GetFolder().wx_str()));
            menu.Append(idMenuOpenFolderFilesPopup, wxString::Format(wxT("打开 %s*"), ftd->GetFolder().wx_str()));
            menu.AppendSeparator();
            menu.Append(idMenuFindFile, wxT("查找文件..."));
        }else if (ftd->GetKind() == FileTreeData::ftdkVirtualGroup){
            /*如果是虚拟组(通配符匹配)*/
            menu.Append(idMenuFindFile, wxT("查找文件..."));
        }

        /*要求任何插件在此菜单中添加项目*/
        Manager::Get()->GetPluginManager()->AskPluginsForModuleMenu(mtProjectManager, &menu, ftd);

        /*更多项目选项*/
        if (ftd->GetKind() == FileTreeData::ftdkProject)
        {
            menu.Append(idMenuTreeProjectProperties, wxT("属性..."));
            menu.Enable(idMenuTreeProjectProperties, PopUpMenuOption);
        }else if (ftd->GetKind() == FileTreeData::ftdkFile){
            /*更多文件选项*/
            menu.AppendSeparator();
            wxMenu *options = new wxMenu;
            wxMenuItem *optionsItem = menu.AppendSubMenu(options, wxT("选项"));
            optionsItem->Enable(PopUpMenuOption);

            options->AppendCheckItem(idMenuTreeOptionsCompile, wxT("编译文件"));
            options->AppendCheckItem(idMenuTreeOptionsLink, wxT("链接文件"));
            options->AppendSeparator();
            options->Append(idMenuTreeOptionsEnableBoth, wxT("都启用"));
            options->Append(idMenuTreeOptionsDisableBoth, wxT("都禁用"));

            if ( ProjectFile* pf = ftd->GetProjectFile() )
            {
                menu.Check(idMenuTreeOptionsCompile, pf->compile);
                menu.Check(idMenuTreeOptionsLink, pf->link);
            }
            menu.Append(idMenuTreeFileProperties, wxT("属性..."));
            menu.Enable(idMenuTreeFileProperties, PopUpMenuOption);
        }
    }else if (!ftd && pm->GetWorkspace()){
        wxCommandEvent event;
        OnRightClick(event);
        return;
    }

    if (menu.GetMenuItemCount() != 0){m_pTree->PopupMenu(&menu, pt);}
}

/*打开文件*/
void ProjectManagerUI::DoOpenFile(ProjectFile* pf, const wxString& filename)
{
    /*基本内容:我们只能打开仍然存在的文件*/
    wxFileName the_file(filename);
    if (!the_file.FileExists())
    {
        wxString msg;
        msg.Printf(wxT("无法打开文件 '%s'.\n文件不存在."), filename.wx_str());
        cbMessageBox(msg, wxT("错误"));
        pf->SetFileState(fvsMissing);
        Manager::Get()->GetLogManager()->LogError(msg);
        return;
    }

    FileType ft = FileTypeOf(filename);
    if (ft == ftHeader || ft == ftSource || ft == ftTemplateSource)
    {
        /*C/C++ 头文件和源文件, 总是在Code::Blocks内部打开*/
        if ( cbEditor* ed = Manager::Get()->GetEditorManager()->Open(filename) )
        {
            ed->SetProjectFile(pf);
            ed->Activate();
        }else{
            wxString msg;
            msg.Printf(wxT("打开文件 '%s' 失败."), filename.wx_str());
            Manager::Get()->GetLogManager()->LogError(msg);
        }
    }
    else
    {
        /*首先查找自定义编辑器如果失败,请尝试MIME处理程序*/
        EditorBase* eb = Manager::Get()->GetEditorManager()->IsOpen(filename);
        if (eb && !eb->IsBuiltinEditor())
        {
            /*自定义编辑器刚刚被激活*/
            eb->Activate();
            return;
        }

        /*不是可识别的文件类型*/
        cbMimePlugin* plugin = Manager::Get()->GetPluginManager()->GetMIMEHandlerForFile(filename);
        if (!plugin)
        {
            wxString msg;
            msg.Printf(wxT("无法打开文件 '%s'.没有为此类文件注册处理程序."), filename.wx_str());
            Manager::Get()->GetLogManager()->LogError(msg);
        }
        else if (plugin->OpenFile(filename) != 0)
        {
            const PluginInfo* info = Manager::Get()->GetPluginManager()->GetPluginInfo(plugin);
            wxString msg;
            msg.Printf(wxT("无法打开文件 '%s'.\n注册的处理程序(%s)无法打开它."), filename.wx_str(), info ? info->title.wx_str() : wxString(wxT("<未知插件>")).wx_str());
            Manager::Get()->GetLogManager()->LogError(msg);
        }
    }
}

/*打开被选定文件*/
void ProjectManagerUI::DoOpenSelectedFile() {
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk()) { return; }

    if (FileTreeData *ftd = (FileTreeData *) m_pTree->GetItemData(sel)) {
        if (ProjectFile *pf = ftd->GetProjectFile()) { DoOpenFile(pf, pf->file.GetFullPath()); }
    }
}

/*递归删除文件*/
void ProjectManagerUI::RemoveFilesRecursively(wxTreeItemId& sel_id)
{
    wxTreeItemIdValue cookie;
    wxTreeItemId child;
    wxString filename;
    size_t i = 0;
    while (i < m_pTree->GetChildrenCount(sel_id))
    {
        if (i == 0){
            child = m_pTree->GetFirstChild(sel_id, cookie);
        }else {
            child = m_pTree->GetNextChild(sel_id, cookie);
        }
        if (child.IsOk())
        {
            if ( FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(child) )
            {
                cbProject* prj = ftd->GetProject();
                if (prj && ftd->GetKind() == FileTreeData::ftdkFile)
                {
                    if ( ProjectFile* pf = ftd->GetProjectFile() )
                        Manager::Get()->GetProjectManager()->RemoveFileFromProject(pf, prj);
                }
                else if (  ftd->GetKind() == FileTreeData::ftdkFolder
                        || ftd->GetKind() == FileTreeData::ftdkVirtualFolder)
                {
                    RemoveFilesRecursively(child);
                }
            }
            ++i;
        }else{break;}
    }
}

/*递归打开文件*/
void ProjectManagerUI::OpenFilesRecursively(wxTreeItemId& sel_id) {
    wxTreeItemIdValue cookie;
    wxTreeItemId child;
    wxString filename;
    size_t i = 0;
    while (i < m_pTree->GetChildrenCount(sel_id)) {
        if (i == 0) {
            child = m_pTree->GetFirstChild(sel_id, cookie);
        } else {
            child = m_pTree->GetNextChild(sel_id, cookie);
        }
        if (child.IsOk()) {
            FileTreeData *ftd = (FileTreeData *) m_pTree->GetItemData(child);
            if (ftd) {
                cbProject *prj = ftd->GetProject();
                if (prj && ftd->GetKind() == FileTreeData::ftdkFile) {
                    if (ProjectFile *pf = ftd->GetProjectFile()) { DoOpenFile(pf, pf->file.GetFullPath()); }
                } else if (ftd->GetKind() == FileTreeData::ftdkFolder || ftd->GetKind() == FileTreeData::ftdkVirtualFolder) {
                    OpenFilesRecursively(child);
                }
            }
            ++i;
        } else { break; }
    }
}

/*处理对应事件,上下文菜单标签切换*/
void ProjectManagerUI::OnTabContextMenu(cb_unused wxAuiNotebookEvent& event)
{
    wxMenu* NBmenu = new wxMenu();
    if (Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/project_tabs_bottom"), false))
        NBmenu->Append(idNB_TabTop, wxT("标签(Tabs)在顶端"));
    else
        NBmenu->Append(idNB_TabBottom, wxT("标签(Tabs)在底部"));
    m_pNotebook->PopupMenu(NBmenu);
    delete NBmenu;
}

/*标签位置响应*/
void ProjectManagerUI::OnTabPosition(wxCommandEvent& event)
{
    long style = m_pNotebook->GetWindowStyleFlag();
    style &= ~wxAUI_NB_BOTTOM;

    if (event.GetId() == idNB_TabBottom) { style |= wxAUI_NB_BOTTOM; }
    m_pNotebook->SetWindowStyleFlag(style);
    m_pNotebook->Refresh();
    /*(style & wxAUI_NB_BOTTOM)仅保存有关选项卡位置的信息*/
    Manager::Get()->GetConfigManager(_T("app"))->Write(_T("/environment/project_tabs_bottom"), (bool)(style & wxAUI_NB_BOTTOM));
}

/*开始拖拉树*/
void ProjectManagerUI::OnTreeBeginDrag(wxTreeEvent& event)
{
    size_t count = m_pTree->GetSelections(m_DraggingSelection);
    for (size_t i = 0; i < count; i++)
    {
        /*我们开始拖动什么项?*/
        wxTreeItemId id = m_DraggingSelection[i];

        if (!id.IsOk()){return;}
        /*如果没有与之关联的数据,则不允许*/
        FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(id);
        if (!ftd){return;}

        /*如果没有项目则不允许*/
        cbProject* prj = ftd->GetProject();
        if (!prj){return;}

        /*仅当项目批准时才允许*/
        if (!ProjectCanDragNode(prj, m_pTree, id)){return;}

    }
    /*允许*/
    event.Allow();
}

/*项目树拖拉结束*/
void ProjectManagerUI::OnTreeEndDrag(wxTreeEvent& event)
{
    wxTreeItemId to = event.GetItem();

    /*拖动目标有效吗?*/
    if (!to.IsOk()){return;}

    /*如果没有任何相关数据,则不允许*/
    FileTreeData* ftdTo = (FileTreeData*)m_pTree->GetItemData(to);
    if (!ftdTo){return;}
    /*如果没有项目或其他项目,则不允许*/
    cbProject* prjTo = ftdTo->GetProject();
    if (!prjTo){return;}

    size_t count = m_DraggingSelection.Count();
    for (size_t i = 0; i < count; i++)
    {
        wxTreeItemId from = m_DraggingSelection[i];

        /*有效的项吗?*/
        if (!from.IsOk()){return;}

        /*如果没有任何相关数据,则不允许*/
        FileTreeData* ftdFrom = (FileTreeData*)m_pTree->GetItemData(from);
        if (!ftdFrom){return;}

        /*如果没有项目或其他项目,则不允许*/
        cbProject* prjFrom = ftdTo->GetProject();
        if (prjFrom != prjTo){return;}
    }

    /*仅当项目批准时才允许*/
    if (!ProjectNodeDragged(prjTo, m_pTree, m_DraggingSelection, to)){return;}

    event.Allow();
}

/*响应项目文件被激活*/
void ProjectManagerUI::OnProjectFileActivated(wxTreeEvent& event) {
    wxTreeItemId id = event.GetItem();
    FileTreeData *ftd = (FileTreeData *) m_pTree->GetItemData(id);
    ProjectManager *pm = Manager::Get()->GetProjectManager();

    if (ftd && ftd->GetKind() == FileTreeData::ftdkProject) {
        if (ftd->GetProject() != pm->GetActiveProject()) { pm->SetProject(ftd->GetProject(), false); }

        /*阻止项目展开状态切换项目激活时,将其切换一次,以便wx将其切换回*/

        m_pTree->IsExpanded(id) ? m_pTree->Collapse(id) : m_pTree->Expand(id);
    } else if (ftd
               && ((ftd->GetKind() == FileTreeData::ftdkVirtualGroup)
                   || (ftd->GetKind() == FileTreeData::ftdkVirtualFolder)
                   || (ftd->GetKind() == FileTreeData::ftdkFolder))) { m_pTree->IsExpanded(id) ? m_pTree->Collapse(id) : m_pTree->Expand(id); }
    else if (!ftd && pm->GetWorkspace()) { m_pTree->IsExpanded(m_TreeRoot) ? m_pTree->Collapse(m_TreeRoot) : m_pTree->Expand(m_TreeRoot); }
    else { DoOpenSelectedFile(); }
}

/*响应解释程序执行参数*/
void ProjectManagerUI::OnExecParameters(cb_unused wxCommandEvent& event)
{
    if (Manager::Get()->GetProjectManager()->GetActiveProject())
        Manager::Get()->GetProjectManager()->GetActiveProject()->SelectTarget(-1, true);
}

/*响应界面右键点击*/
void ProjectManagerUI::OnRightClick(cb_unused wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if (!pm){return;}

    bool notCompilingProject = true;
    cbProject *project = pm->GetActiveProject();
    if (project && project->GetCurrentlyCompilingTarget()){notCompilingProject = false;}

    wxMenu menu;
    if (pm->GetWorkspace())
    {
        menu.Append(idMenuTreeRenameWorkspace, wxT("重命名工作区..."));
        menu.Enable(idMenuTreeRenameWorkspace, notCompilingProject);
        menu.AppendSeparator();
        menu.Append(idMenuTreeSaveWorkspace, wxT("保存工作区"));
        menu.Enable(idMenuTreeSaveWorkspace, notCompilingProject);
        menu.Append(idMenuTreeSaveAsWorkspace, wxT("把工作区保存为..."));
        menu.Enable(idMenuTreeSaveAsWorkspace, notCompilingProject);
        menu.AppendSeparator();
        menu.Append(idMenuFindFile, wxT("查找文件..."));
    }

    /*要求任何插件在此菜单中添加项目*/
    Manager::Get()->GetPluginManager()->AskPluginsForModuleMenu(mtProjectManager, &menu);

    /*如果此菜单中添加了插件,请添加分隔符*/
    if (menu.GetMenuItemCount() != 0){menu.AppendSeparator();}

    menu.AppendCheckItem(idMenuViewCategorizePopup,     wxT("按文件类型分类"));
    menu.AppendCheckItem(idMenuViewUseFoldersPopup,     wxT("在磁盘上显示文件夹"));
    menu.AppendCheckItem(idMenuViewHideFolderNamePopup, wxT("隐藏文件夹名称"));

    bool do_categorise       = (m_TreeVisualState&ptvsCategorize);
    bool do_use_folders      = (m_TreeVisualState&ptvsUseFolders);
    /*"使用文件夹(use folders)"优先*/
    bool do_hide_folder_name = !do_use_folders && (m_TreeVisualState&ptvsHideFolderName);

    menu.Check(idMenuViewCategorizePopup,     do_categorise);
    menu.Check(idMenuViewUseFoldersPopup,     do_use_folders);
    menu.Check(idMenuViewHideFolderNamePopup, do_hide_folder_name);

    menu.Enable(idMenuViewUseFoldersPopup,     !do_hide_folder_name);
    menu.Enable(idMenuViewHideFolderNamePopup, !do_use_folders);

    menu.AppendSeparator();
    menu.Append(idMenuViewFileMasks, wxT("编辑文件类型和类别..."));
    menu.Enable(idMenuViewFileMasks, notCompilingProject);

    if (pm->GetWorkspace())
    {
        /*这个菜单项应该总是最后一个*/
        menu.AppendSeparator();
        menu.Append(idMenuTreeCloseWorkspace,  wxT("关闭工作区"));
        menu.Enable(idMenuTreeCloseWorkspace, notCompilingProject);
    }

    wxPoint pt = wxGetMousePosition();
    pt = m_pTree->ScreenToClient(pt);
    m_pTree->PopupMenu(&menu, pt);
}

/*响应树项右键点击*/
void ProjectManagerUI::OnTreeItemRightClick(wxTreeEvent& event)
{
    if (Manager::Get()->GetProjectManager()->IsLoadingProject())
    {
        wxBell();
        return;
    }

    /*我们有一个弹出菜单,因此我们将使用右键单击项而不是第一个树选择.*/
    m_RightClickItem = event.GetItem();

    m_pTree->SelectItem(event.GetItem());
    ShowMenu(event.GetItem(), event.GetPoint());

    /*取消设置,以便我们再次使用第一个树选择.*/
    m_RightClickItem.Unset();
}

/*响应重命名工作区*/
void ProjectManagerUI::OnRenameWorkspace(cb_unused wxCommandEvent& event)
{
    cbWorkspace* wkspc = Manager::Get()->GetProjectManager()->GetWorkspace();
    if (wkspc)
    {
        wxString text = cbGetTextFromUser(wxT("请输入工作区的新名称:"),wxT("重命名工作区"),wkspc->GetTitle());
        if (!text.IsEmpty())
        {
            wkspc->SetTitle(text);
            m_pTree->SetItemText(m_TreeRoot, wkspc->GetTitle());
        }
    }
}

/*响应保存工作区*/
void ProjectManagerUI::OnSaveWorkspace(cb_unused wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if (pm->GetWorkspace())
        pm->SaveWorkspace();
}

/*响应另存为工作区*/
void ProjectManagerUI::OnSaveAsWorkspace(cb_unused wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if (pm->GetWorkspace())
        pm->SaveWorkspaceAs(_T(""));
}

/*响应关闭工作区*/
void ProjectManagerUI::OnCloseWorkspace(cb_unused wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if (pm->GetWorkspace())
        pm->CloseWorkspace();
}

/*响应设置项目作为激活状态*/
void ProjectManagerUI::OnSetActiveProject(wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    ProjectsArray* pa = pm->GetProjects();

    if (event.GetId() == idMenuSetActiveProject)
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;

        FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
        if (!ftd)
            return;

        pm->SetProject(ftd->GetProject(), false);
    }
    else if (event.GetId() == idMenuPriorProject)
    {
        int index = pa->Index(pm->GetActiveProject());
        if (index == wxNOT_FOUND)
            return;
        --index;
        if (index < 0)
            index = pa->GetCount() - 1;
        pm->SetProject(pa->Item(index), false);
    }
    else if (event.GetId() == idMenuNextProject)
    {
        int index = pa->Index(pm->GetActiveProject());
        if (index == wxNOT_FOUND)
            return;
        ++index;
        if (index == (int)pa->GetCount())
            index = 0;
        pm->SetProject(pa->Item(index), false);
    }
    else if (event.GetId() == idMenuProjectUp)
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;
        if ( FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel) )
            MoveProjectUp(ftd->GetProject());
    }
    else if (event.GetId() == idMenuProjectDown)
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;

        if ( FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel) )
            MoveProjectDown(ftd->GetProject());
    }
}

/*响应递归添加文件到项目中*/
void ProjectManagerUI::OnAddFilesToProjectRecursively(wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    cbProject* prj = nullptr;
    wxString basePath;

    if (event.GetId() == idMenuAddFilesRecursively)
    {
        prj = pm->GetActiveProject();
        if (prj)
            basePath = prj->GetBasePath();
    }
    else
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;

        if ( FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel) )
        {
            if ( (prj = ftd->GetProject()) )
            {
                basePath = ftd->GetFolder();
                if (!wxDirExists(basePath))
                    basePath = prj->GetBasePath();
            }
        }
    }
    if (!prj)
        return;

    wxString dir = ChooseDirectory(m_pTree,wxT("递归添加文件..."),basePath,wxEmptyString,false,false);
    if (dir.IsEmpty())
        return;

    wxArrayInt targets;
    /*只有在不止一个的情况下才要求目标*/
    if (prj->GetBuildTargetsCount() == 1) { targets.Add(0); }

    /*生成要添加的文件列表*/
    wxArrayString array;
    wxDir::GetAllFiles(dir, &array, wxEmptyString, wxDIR_FILES | wxDIR_DIRS);
    if (array.GetCount() == 0) { return; }

    /*出于可用性原因,请从列表中删除任何目录项...*/
    unsigned int i = 0;
    while (i < array.GetCount())
    {
        /*放弃目录,以及一些众所周知的SCMs控制文件夹也放弃C::B项目文件*/

        if (wxDirExists(array[i]) ||
            array[i].Contains(_T("/.git/")) ||
            array[i].Contains(_T("\\.git\\")) ||
            array[i].Contains(_T("\\.hg\\")) ||
            array[i].Contains(_T("/.hg/")) ||
            array[i].Contains(_T("\\.svn\\")) ||
            array[i].Contains(_T("/.svn/")) ||
            array[i].Contains(_T("\\CVS\\")) ||
            array[i].Contains(_T("/CVS/")) ||
            array[i].Lower().Matches(_T("*.cbp")))
        {
            array.RemoveAt(i);
        }
        else
            ++i;
    }

    wxString wild;
    const FilesGroupsAndMasks* fgam = pm->GetFilesGroupsAndMasks();
    for (unsigned fm_idx = 0; fm_idx < fgam->GetGroupsCount(); fm_idx++)
        wild += fgam->GetFileMasks(fm_idx);

    MultiSelectDlg dlg(nullptr, array, wild, wxT("选择要添加到项目中的文件:"));
    PlaceWindow(&dlg);
    if (dlg.ShowModal() != wxID_OK)
        return;
    array = dlg.GetSelectedStrings();

    /*最后添加文件*/
    pm->AddMultipleFilesToProject(array, prj, targets);
    RebuildTree();
}

/*响应添加文件到项目*/
void ProjectManagerUI::OnAddFileToProject(wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    cbProject* prj = nullptr;
    wxString basePath;

    if (event.GetId() == idMenuAddFile)
    {
        prj = pm->GetActiveProject();
        if (prj)
            basePath = prj->GetBasePath();
    }
    else
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;

        if ( FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel) )
        {
            if ( (prj = ftd->GetProject()) )
            {
                basePath = ftd->GetFolder();
                if (!wxDirExists(basePath))
                    basePath = prj->GetBasePath();
            }
        }
    }

    if (!prj) { return; }

    wxFileDialog dlg(Manager::Get()->GetAppWindow(),
                     wxT("添加文件到项目..."),
                    basePath,
                    wxEmptyString,
                    FileFilters::GetFilterString(),
                    wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST | compatibility::wxHideReadonly);
    dlg.SetFilterIndex(FileFilters::GetIndexForFilterAll());

    PlaceWindow(&dlg);
    if (dlg.ShowModal() == wxID_OK)
    {
        wxArrayInt targets;
        /*只有在不止一个的情况下才要求目标*/
        if (prj->GetBuildTargetsCount() == 1) { targets.Add(0); }

        wxArrayString array;
        dlg.GetPaths(array);
        pm->AddMultipleFilesToProject(array, prj, targets);
        RebuildTree();
    }
}

namespace
{
    /*查找文件*/
void FindFiles(wxArrayString &resultFiles, wxTreeCtrl &tree, wxTreeItemId item)
{
    FileTreeData* ftd = static_cast<FileTreeData*>(tree.GetItemData(item));
    if (!ftd) { return; }

    switch (ftd->GetKind())
    {
        case FileTreeData::ftdkFile:
            resultFiles.Add(ftd->GetProjectFile()->relativeFilename);
            break;
        case FileTreeData::ftdkFolder:
            {
                wxTreeItemIdValue cookie;
                wxTreeItemId i = tree.GetFirstChild(item, cookie);
                while (i.IsOk())
                {
                    FindFiles(resultFiles, tree, i);
                    i = tree.GetNextChild(item, cookie);
                }
            }
            break;
        case FileTreeData::ftdkUndefined:     // fall-through
        case FileTreeData::ftdkProject:       // fall-through
        case FileTreeData::ftdkVirtualGroup:  // fall-through
        case FileTreeData::ftdkVirtualFolder: // fall-through
        default:
            for (FilesList::iterator it = ftd->GetProject()->GetFilesList().begin(); it != ftd->GetProject()->GetFilesList().end(); ++it)
                resultFiles.Add(((ProjectFile*)*it)->relativeFilename);
    }
}
} // namespace

/*响应移除项目中的文件*/
void ProjectManagerUI::OnRemoveFileFromProject(wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk()) { return; }

    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
    if (!ftd) { return; }

    cbProject* prj = ftd->GetProject();
    if (!prj) { return; }

    wxString oldpath = prj->GetCommonTopLevelPath();

    if (event.GetId() == idMenuRemoveFile)
    {
        /*移除多个文件*/
        wxArrayString files;
        FindFiles(files, *m_pTree, sel);

        if (files.Count()==0)
        {
            cbMessageBox(wxT("此项目不包含任何要移除的文件."),wxT("错误"), wxICON_WARNING);
            return;
        }
        files.Sort();
        wxString msg;
        msg.Printf(wxT("选择要从 %s 移除的文件:"), prj->GetTitle().wx_str());
        MultiSelectDlg dlg(nullptr, files, true, msg);
        PlaceWindow(&dlg);
        if (dlg.ShowModal() == wxID_OK)
        {
            wxArrayInt indices = dlg.GetSelectedIndices();
            if (indices.GetCount() == 0)
                return;
            if (cbMessageBox(wxT("确实要从项目中移除这些文件吗?"),wxT("确认"),wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT) != wxID_YES)
            {
                return;
            }
            prj->BeginRemoveFiles();
            /*我们向后迭代数组,因为如果我们正常地迭代它,当我们删除第一个索引时,其余的就变得无效...*/

            for (int i = (int)indices.GetCount() - 1; i >= 0; --i)
            {
                Manager::Get()->GetLogManager()->DebugLog(wxString::Format(wxT("移除索引值: %d"), indices[i]));

                if ( ProjectFile* pf = prj->GetFileByFilename(files[indices[i]]) )
                    pm->RemoveFileFromProject(pf, prj);
            }
            prj->CalculateCommonTopLevelPath();
            prj->EndRemoveFiles();
            RebuildTree();
        }
    }
    else if (event.GetId() == idMenuRemoveFilePopup)
    {
        if ( ProjectFile* pf = ftd->GetProjectFile() )
        {
            // remove single file
            // 移除单个文件
            prj->BeginRemoveFiles();
            pm->RemoveFileFromProject(pf, prj);
            prj->CalculateCommonTopLevelPath();
            if (prj->GetCommonTopLevelPath() == oldpath)
                m_pTree->Delete(sel);
            prj->EndRemoveFiles();
            RebuildTree();
        }
    }
    else if (event.GetId() == idMenuRemoveFolderFilesPopup)
    {
        // remove all files from a folder
        if (cbMessageBox(wxT("确实要从项目中递归移除此文件夹下的所有文件吗?"),
                         wxT("确认"),
                         wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT) != wxID_YES)
        {
            return;
        }
        bool is_virtual = ftd->GetKind() == FileTreeData::ftdkVirtualFolder;
        if (is_virtual || ftd->GetKind() == FileTreeData::ftdkFolder)
        {
            prj->BeginRemoveFiles();
            RemoveFilesRecursively(sel);
            prj->EndRemoveFiles();
        }
        prj->CalculateCommonTopLevelPath();
        if (prj->GetCommonTopLevelPath() == oldpath && !is_virtual)
            m_pTree->Delete(sel);
        else if (is_virtual)
            ProjectVirtualFolderDeleted(prj, m_pTree, sel);
        RebuildTree();
    }
}

//响应保存项目
void ProjectManagerUI::OnSaveProject(wxCommandEvent& WXUNUSED(event))
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    ProjectManager* pm = Manager::Get()->GetProjectManager();

    if ( FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel) )
    {
        if ( cbProject* prj = ftd->GetProject() )
        {
            // TODO : does it make sense NOT to save project file while compiling ??
            // TODO: 编译时不保存项目文件有意义吗??
            if (pm->IsLoadingProject() || prj->GetCurrentlyCompilingTarget())
                wxBell();
            else
                pm->SaveProject(prj);
        }
    }
}

//响应关闭项目
void ProjectManagerUI::OnCloseProject(wxCommandEvent& WXUNUSED(event))
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if (pm->IsLoadingProject())
    {
        wxBell();
        return;
    }

    wxArrayTreeItemIds selections;
    int count = m_pTree->GetSelections(selections);
    if (count == 0)
        return;
    std::set<cbProject*> projectsToClose;

    for (size_t ii = 0; ii < selections.GetCount(); ++ii)
    {
        FileTreeData* ftd = reinterpret_cast<FileTreeData*>(m_pTree->GetItemData(selections[ii]));
        if (!ftd || ftd->GetKind() != FileTreeData::ftdkProject)
            continue;

        cbProject* prj = ftd->GetProject();
        if (prj)
        {
            if (prj->GetCurrentlyCompilingTarget())
                wxBell();
            else
                projectsToClose.insert(prj);
        }
    }

    for (std::set<cbProject*>::iterator it = projectsToClose.begin(); it != projectsToClose.end(); ++it)
        pm->CloseProject(*it);

    if (pm->GetProjects()->GetCount() > 0 && !pm->GetActiveProject())
        pm->SetProject(pm->GetProjects()->Item(0), false);

    Manager::Get()->GetAppWindow()->Refresh();
}

//响应保存文件
void ProjectManagerUI::OnSaveFile(wxCommandEvent& WXUNUSED(event))
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    if (FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel))
    {
        if ( ProjectFile* pf = ftd->GetProjectFile() )
            Manager::Get()->GetEditorManager()->Save(pf->file.GetFullPath());
    }
}

//响应关闭文件
void ProjectManagerUI::OnCloseFile(wxCommandEvent& WXUNUSED(event))
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    if (FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel))
    {
        if ( ProjectFile* pf = ftd->GetProjectFile() )
            Manager::Get()->GetEditorManager()->Close(pf->file.GetFullPath());
    }
}

//响应打开文件
void ProjectManagerUI::OnOpenFile(wxCommandEvent& WXUNUSED(event))
{
    DoOpenSelectedFile();
}

//响应打开目录下文件
void ProjectManagerUI::OnOpenFolderFiles(wxCommandEvent& event)
{
    wxTreeItemId sel = GetTreeSelection();
    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
    if (!ftd)
        return;

    // open all files from a folder
    if (cbMessageBox(wxT("确实要从项目中递归打开此文件夹下的所有文件吗?"),
                     wxT("确认"),
                     wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT) != wxID_YES)
    {
        return;
    }

    if (   ftd->GetKind() == FileTreeData::ftdkFolder
        || ftd->GetKind() == FileTreeData::ftdkVirtualFolder )
    {
        OpenFilesRecursively(sel);
    }

    event.Skip();
}

//响应打开方式
void ProjectManagerUI::OnOpenWith(wxCommandEvent& event)
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
    if (!ftd)
        return;

    if ( ProjectFile* pf = ftd->GetProjectFile() )
    {
        wxString filename = pf->file.GetFullPath();
        if (event.GetId() == idOpenWithInternal)
        {
            if ( cbEditor* ed = Manager::Get()->GetEditorManager()->Open(filename) )
            {
                ed->SetProjectFile(pf);
                ed->Show(true);
                return;
            }
        }
        else
        {
            PluginsArray mimes = Manager::Get()->GetPluginManager()->GetMimeOffers();
            cbMimePlugin* plugin = (cbMimePlugin*)mimes[event.GetId() - idOpenWith[0]];
            if (plugin && plugin->OpenFile(filename) == 0)
                return;
        }
        wxString msg;
        msg.Printf(wxT("打开文件 '%s' 失败."), filename.wx_str());
        Manager::Get()->GetLogManager()->LogError(msg);
    }
}
//响应注释菜单
void ProjectManagerUI::OnNotes(wxCommandEvent& WXUNUSED(event))
{
    if ( cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject() )
        prj->ShowNotes(false, true);
}

//响应属性菜单项
void ProjectManagerUI::OnProperties(wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    cbProject* activePrj = pm->GetActiveProject();
    if (event.GetId() == idMenuProjectProperties)
    {
        wxString backupTitle = activePrj ? activePrj->GetTitle() : wxT("");
        if (ProjectShowOptions(activePrj))
        {
            // make sure that cbEVT_PROJECT_ACTIVATE
            // is sent (maybe targets have changed)...
            // rebuild tree  only if title has changed
            //
            // 确保发送cbEVT_PROJECT_ACTIVATE(可能目标已更改)...
            // 仅当标题已更改时重建树
            pm->SetProject(activePrj, backupTitle != activePrj->GetTitle());
        }
    }
    else if (event.GetId() == idMenuTreeProjectProperties)
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;

        FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);

        cbProject* prj = ftd ? ftd->GetProject() : activePrj;
        wxString backupTitle = prj ? prj->GetTitle() : wxT("");
        if (ProjectShowOptions(prj) && prj == activePrj)
        {
            // rebuild tree and make sure that cbEVT_PROJECT_ACTIVATE
            // is sent (maybe targets have changed)...
            // rebuild tree  only if title has changed
            //
            // 重新生成树并确保发送了cbEVT_PROJECT_ACTIVATE(可能目标已更改)...
            // 仅当标题已更改时重建树

            pm->SetProject(prj, backupTitle != prj->GetTitle());
        }
        // if project title has changed, update the appropriate tab tooltips
        // 如果项目标题已更改,请更新相应的选项卡工具提示
        wxString newTitle = prj->GetTitle();
        if (backupTitle != newTitle)
        {
            cbAuiNotebook* nb = Manager::Get()->GetEditorManager()->GetNotebook();
            if (nb)
            {
                wxString toolTip;
                for (size_t i = 0; i < nb->GetPageCount(); ++i)
                {
                    toolTip = nb->GetPageToolTip(i);
                    if (toolTip.EndsWith(wxT("项目: ") + backupTitle))
                    {
                        toolTip.Replace(wxT("项目: ") + backupTitle,wxT("项目: ") + newTitle);
                        nb->SetPageToolTip(i, toolTip);
                    }
                }
            }
        }
    }
    else if (event.GetId() == idMenuTreeFileProperties)
    {
        wxTreeItemId sel = GetTreeSelection();
        if (!sel.IsOk())
            return;

        FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);

        cbProject* prj = ftd ? ftd->GetProject() : activePrj;
        if (prj)
        {
            if (ftd && ftd->GetFileIndex() != -1)
            {
                if ( ProjectFile* pf = ftd->GetProjectFile() )
                    pf->ShowOptions(Manager::Get()->GetAppWindow());
            }
        }
    }
    else
    {
        // active editor properties
        // 激活编辑器属性
        if ( cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor() )
        {
            if ( ProjectFile* pf = ed->GetProjectFile() )
                pf->ShowOptions(Manager::Get()->GetAppWindow());
            else
            {
                // active editor not-in-project
                // 活动编辑器不在项目中
                ProjectFileOptionsDlg dlg(Manager::Get()->GetAppWindow(), ed->GetFilename());
                PlaceWindow(&dlg);
                dlg.ShowModal();
            }
        }
    }
}

/// Find all selected tree items which are files and call the func on them.
/// The function is expected to return true when it made modifications to the ProjectFile parameter,
/// and false when it didn't.
/// The modified parameter will be filled with the set of modified projects.
///
/// 查找作为文件的所有选定树项并对其调用func.
/// 该函数在修改项目文件(ProjectFile)参数时应返回true,否则返回false.
/// 修改后的参数将填入修改后的项目集.

//应用文件选项改变

template<typename Func>
static void applyFileOptionChange(std::set<cbProject*> &modified, wxTreeCtrl &tree, Func func)
{
    wxArrayTreeItemIds selected;
    size_t count = tree.GetSelections(selected);
    for (size_t ii = 0; ii < count; ++ii)
    {
        wxTreeItemId id = selected[ii];
        if (!id.IsOk()){continue;}

        FileTreeData* ftd = (FileTreeData*)tree.GetItemData(id);
        if (!ftd || ftd->GetKind() != FileTreeData::ftdkFile){continue;}

        ProjectFile* pf = ftd->GetProjectFile();
        if (pf && func(*pf))
        {
            if (pf->GetParentProject())
                modified.insert(pf->GetParentProject());
        }
    }
}

//响应文件选项菜单
void ProjectManagerUI::OnFileOptions(wxCommandEvent &event)
{
    std::set<cbProject*> modified;
    if (event.GetId() == idMenuTreeOptionsCompile)
    {
        const bool checked = event.IsChecked();
        applyFileOptionChange(modified, *m_pTree, [checked](ProjectFile &pf) -> bool {
            if (pf.compile != checked)
            {
                pf.compile = checked;
                return true;
            }
            else
                return false;
        });
    }
    else if (event.GetId() == idMenuTreeOptionsLink)
    {
        const bool checked = event.IsChecked();
        applyFileOptionChange(modified, *m_pTree, [checked](ProjectFile &pf) -> bool {
            if (pf.link != checked)
            {
                pf.link = checked;
                return true;
            }
            else
                return false;
        });
    }
    else if (event.GetId() == idMenuTreeOptionsEnableBoth || event.GetId() == idMenuTreeOptionsDisableBoth)
    {
        const bool newValue = (event.GetId() == idMenuTreeOptionsEnableBoth);
        applyFileOptionChange(modified, *m_pTree, [newValue](ProjectFile &pf) -> bool {
            pf.compile = pf.link = newValue;
            return true;
        });
    }

    for (std::set<cbProject*>::iterator it = modified.begin(); it != modified.end(); ++it)
        (*it)->SetModified(true);
    if (!modified.empty())
        RebuildTree();
    event.Skip();
}

//项目文件路径关联比较结构
struct ProjectFileRelativePathCmp
{
    ProjectFileRelativePathCmp(cbProject* pActiveProject) : m_pActiveProject(pActiveProject) {}
    bool operator()(ProjectFile* pf1, ProjectFile* pf2)
    {
        if (pf1->GetParentProject() == m_pActiveProject && pf2->GetParentProject() != m_pActiveProject)
            return true;
        else if (pf1->GetParentProject() != m_pActiveProject && pf2->GetParentProject() == m_pActiveProject)
            return false;
        else
        {
            int relCmp = pf1->relativeFilename.Cmp(pf2->relativeFilename);

            if (relCmp == 0)
                return pf1 < pf2;
            else
                return relCmp < 0;
        }
    }
private:
    cbProject* m_pActiveProject;
};

//cb字符串哈希表[wx>3.0.0返回宽字符]
struct cbStringHash
{
    size_t operator()(const wxString& s) const
    {
#if wxCHECK_VERSION(3, 0, 0)
        return std::hash<std::wstring>()(s.ToStdWstring());
#else
        return std::hash<std::wstring>()(s.wc_str());
#endif // wxCHECK_VERSION
    }
};

//响应转到文件
void ProjectManagerUI::OnGotoFile(cb_unused wxCommandEvent& event)
{
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    cbProject* activePrj = pm->GetActiveProject();

    if (!activePrj)
    {
        Manager::Get()->GetLogManager()->DebugLog(wxT("没有活动项目!"));
        return;
    }

    ProjectsArray* pa = pm->GetProjects();

    std::unordered_map<wxString, ProjectFile*, cbStringHash> uniqueAbsPathFiles;
    for (size_t prjIdx = 0; prjIdx < pa->GetCount(); ++prjIdx)
    {
        cbProject* prj = (*pa)[prjIdx];
        if (!prj) {continue;}

        for (FilesList::iterator it = prj->GetFilesList().begin(); it != prj->GetFilesList().end(); ++it)
        {
            ProjectFile *projectFile = *it;
            uniqueAbsPathFiles.insert({projectFile->file.GetFullPath(), projectFile});
        }
    }

    typedef std::vector<ProjectFile*> VProjectFiles;
    VProjectFiles pfiles;
    if (!uniqueAbsPathFiles.empty())
    {
        pfiles.reserve(uniqueAbsPathFiles.size());
        for (const auto &pf : uniqueAbsPathFiles)
            pfiles.push_back(pf.second);
        std::sort(pfiles.begin(), pfiles.end(), ProjectFileRelativePathCmp(activePrj));
    }

    struct Iterator : IncrementalSelectIteratorIndexed {
        Iterator(VProjectFiles &pfiles, bool showProject) :
                m_pfiles(pfiles),
                m_ShowProject(showProject),
                m_ColumnWidth(300) {
        }

        int GetTotalCount() const override {
            return m_pfiles.size();
        }

        const wxString &GetItemFilterString(int index) const override {
            return m_pfiles[index]->relativeFilename;
        }

        wxString GetDisplayText(int index, int column) const override {
            ProjectFile *pf = m_pfiles[m_indices[index]];
            return MakeDisplayName(*pf);
        }

        int GetColumnWidth(int column) const override {
            return m_ColumnWidth;
        }

        void CalcColumnWidth(wxListCtrl &list) override {
            int length = 0;
            ProjectFile *pfLongest = nullptr;
            for (const auto &pf : m_pfiles) {
                int pfLength = pf->relativeFilename.length();
                if (m_ShowProject)
                    pfLength += pf->GetParentProject()->GetTitle().length() + 3;
                if (pfLength > length) {
                    length = pfLength;
                    pfLongest = pf;
                }
            }
            if (pfLongest) {
                const wxString &longestString = MakeDisplayName(*pfLongest);
                int yTemp;
                list.GetTextExtent(longestString, &m_ColumnWidth, &yTemp);
                // just to be safe if the longest string is made of thin letters.
                // 为了安全起见,如果最长的字符串是由细字母组成的.
                m_ColumnWidth += 50;
            } else { m_ColumnWidth = 300; }
        }

    private:
        wxString MakeDisplayName(ProjectFile &pf) const {
            if (m_ShowProject)
                return pf.relativeFilename + wxT(" (") + pf.GetParentProject()->GetTitle() + wxT(")");
            else
                return pf.relativeFilename;
        }

    private:
        const VProjectFiles &m_pfiles;
        wxString temp;
        bool m_ShowProject;
        int m_ColumnWidth;
    };

    Iterator iterator(pfiles, (pa->GetCount() > 1));
    GotoFile dlg(Manager::Get()->GetAppWindow(), &iterator, wxT("选择文件..."), wxT("请选择要打开的文件:"));
    PlaceWindow(&dlg);
    if (dlg.ShowModal() == wxID_OK)
    {
        int selection = dlg.GetSelection();
        if (selection >= 0 && selection < int(pfiles.size()))
            DoOpenFile(pfiles[selection], pfiles[selection]->file.GetFullPath());
    }
}

//响应视图类别
void ProjectManagerUI::OnViewCategorize(wxCommandEvent& event) {
    bool do_categorise = event.IsChecked();
    if (do_categorise) { m_TreeVisualState |= ptvsCategorize; }
    else { m_TreeVisualState &= ~ptvsCategorize; }
    Manager::Get()->GetAppFrame()->GetMenuBar()->Check(idMenuViewCategorize, do_categorise);
    Manager::Get()->GetConfigManager(_T("project_manager"))->Write(_T("/categorize_tree"), do_categorise);
    RebuildTree();
}

//响应视图使用路径
void ProjectManagerUI::OnViewUseFolders(wxCommandEvent& event)
{
    bool do_use_folders = event.IsChecked();

    if (do_use_folders)
        {m_TreeVisualState |=  ptvsUseFolders;}
    else
        {m_TreeVisualState &= ~ptvsUseFolders;}

    Manager::Get()->GetAppFrame()->GetMenuBar()->Check(idMenuViewUseFolders, do_use_folders);
    Manager::Get()->GetAppFrame()->GetMenuBar()->Enable(idMenuViewHideFolderName, !do_use_folders);
    Manager::Get()->GetConfigManager(_T("project_manager"))->Write(_T("/use_folders"), do_use_folders);

    // Do not create an invalid state
    // 不创建无效状态
    if (do_use_folders)
    {
        m_TreeVisualState &= ~ptvsHideFolderName;
        Manager::Get()->GetAppFrame()->GetMenuBar()->Check(idMenuViewHideFolderName, false);
        Manager::Get()->GetConfigManager(_T("project_manager"))->Write(_T("/hide_folder_name"), false);
    }

    RebuildTree();
}

//响应查看隐藏的路径名
void ProjectManagerUI::OnViewHideFolderName(wxCommandEvent& event)
{
    bool do_hide_folder_name = event.IsChecked();

    if (do_hide_folder_name)
        {m_TreeVisualState |=  ptvsHideFolderName;}
    else
        {m_TreeVisualState &= ~ptvsHideFolderName;}

    Manager::Get()->GetAppFrame()->GetMenuBar()->Check(idMenuViewHideFolderName, do_hide_folder_name);
    Manager::Get()->GetAppFrame()->GetMenuBar()->Enable(idMenuViewUseFolders, !do_hide_folder_name);
    Manager::Get()->GetConfigManager(_T("project_manager"))->Write(_T("/hide_folder_name"), do_hide_folder_name);

    // Do not create an invalid state
    // 不创建无效状态
    if (do_hide_folder_name)
    {
        m_TreeVisualState &= ~ptvsUseFolders;
        Manager::Get()->GetAppFrame()->GetMenuBar()->Check(idMenuViewUseFolders, false);
        Manager::Get()->GetConfigManager(_T("project_manager"))->Write(_T("/use_folders"), false);
    }

    RebuildTree();
}

//响应查看文件掩码
void ProjectManagerUI::OnViewFileMasks(cb_unused wxCommandEvent& event)
{
    FilesGroupsAndMasks* fgam = Manager::Get()->GetProjectManager()->GetFilesGroupsAndMasks();
    ProjectsFileMasksDlg dlg(Manager::Get()->GetAppWindow(), fgam);
    PlaceWindow(&dlg);
    if (dlg.ShowModal() == wxID_OK)
    {
        fgam->Save();
        RebuildTree();
    }
}

//取列表节点
wxArrayString ProjectManagerUI::ListNodes(wxTreeItemId node) const
{
    wxArrayString nodes;
    wxTreeItemIdValue cookie;
    wxTreeItemId item = m_pTree->GetFirstChild(node, cookie);
    while (item.IsOk())
    {
        nodes.Add(m_pTree->GetItemText(item));
        if (m_pTree->ItemHasChildren(item))
        {
            const wxArrayString& children = ListNodes(item);
            const wxString parent = nodes.Last();
            for (size_t i = 0; i < children.GetCount(); ++i)
                nodes.Add(parent + wxT("/") + children[i]);
        }
        item = m_pTree->GetNextChild(node, cookie);
    }
    return nodes;
}

//响应查找文件
void ProjectManagerUI::OnFindFile(cb_unused wxCommandEvent& event)
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;
    wxArrayString files = ListNodes(sel);
    if (files.IsEmpty())
        return;

    ProjectManager* pm = Manager::Get()->GetProjectManager();

    // workspace selected, add *.cbp filenames
    // 选择工作区,添加*.cbp文件名
    ConfigManagerContainer::StringToStringMap fileNameMap;
    if ( pm->GetWorkspace() && !(FileTreeData*)m_pTree->GetItemData(sel) )
    {
        for (size_t i = 0; i < pm->GetProjects()->GetCount(); ++i)
        {
            const cbProject* prj = pm->GetProjects()->Item(i);
            const wxFileName file(prj->GetFilename());
            files.Add(file.GetFullName());
            fileNameMap[file.GetFullName()] = prj->GetTitle();
        }
    }

    struct Iterator : IncrementalSelectIteratorIndexed
    {
        Iterator(const wxArrayString &files) : m_files(files), m_ColumnWidth(300)
        {
        }

        int GetTotalCount() const override
        {
            return m_files.size();
        }
        const wxString& GetItemFilterString(int index) const override
        {
            return m_files[index];
        }
        wxString GetDisplayText(int index, int column) const override
        {
            return m_files[m_indices[index]];
        }

        int GetColumnWidth(int column) const override
        {
            return m_ColumnWidth;
        }

        void CalcColumnWidth(wxListCtrl &list) override
        {
            int index = -1;
            size_t length = 0;
            for (size_t ii = 0; ii < m_files.size(); ++ii)
            {
                size_t itemLength = m_files[ii].length();
                if (itemLength > length)
                {
                    index = ii;
                    length = itemLength;
                }
            }
            if (index >= 0 && index < int(m_files.size()))
            {
                int yTemp;
                list.GetTextExtent(m_files[index], &m_ColumnWidth, &yTemp);
                // just to be safe if the longest string is made of thin letters.
                // 为了安全起见,如果最长的字符串是由细字母组成的.
                m_ColumnWidth += 50;
            }
            else
                m_ColumnWidth = 300;
        }

    private:
        const wxArrayString &m_files;
        int m_ColumnWidth;
    };
    Iterator iter(files);
    GotoFile dlg(Manager::Get()->GetAppWindow(), &iter, wxT("查找文件..."),
                 wxT("请输入正在搜索的文件名:"));

    ConfigManager *cfg = Manager::Get()->GetConfigManager(wxT("project_manager"));

    // Add a checkbox at the bottom that control if the selected file will be opened in an editor.
    // 在底部添加一个复选框,用于控制所选文件是否将在编辑器中打开.
    wxCheckBox *chkOpen = new wxCheckBox(&dlg, wxID_ANY, wxT("打开文件"));
    chkOpen->SetValue(cfg->ReadBool(wxT("/find_file_open"), false));
    dlg.AddControlBelowList(chkOpen);

    PlaceWindow(&dlg);
    if (dlg.ShowModal() != wxID_OK)
        return;
    const long selection = dlg.GetSelection();
    if (selection == wxNOT_FOUND)
        return;

    wxString file = files[selection];
    ConfigManagerContainer::StringToStringMap::iterator it = fileNameMap.find(file);
    // resolve .cbp project filename
    // 解析.cbp项目文件名
    if (it != fileNameMap.end())
        file = it->second;
    wxTreeItemIdValue cookie;
    wxTreeItemId item = m_pTree->GetFirstChild(sel, cookie);
    while (item.IsOk())
    {
        // found it, exit
        // 找到了,离开
        if (m_pTree->GetItemText(item) == file)
            break;
        else if (file.StartsWith(m_pTree->GetItemText(item) + wxT("/")))
        {
            // expand node
            file = file.Mid(m_pTree->GetItemText(item).Length() + 1);
            sel  = item;
            item = m_pTree->GetFirstChild(sel, cookie);
        }
        else // try next node
            item = m_pTree->GetNextChild(sel, cookie);
    }
    if (item.IsOk())
    {
        m_pTree->UnselectAll();
        m_pTree->SelectItem(item);
        const FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(item);
        if (ftd && chkOpen->IsChecked())
        {
            // open the file
            if ( ProjectFile* pf = ftd->GetProjectFile() )
                {DoOpenFile(pf, pf->file.GetFullPath());}
            else if (   ftd->GetKind() == FileTreeData::ftdkProject
                     && ftd->GetProject() )
            {
                // change active project
                pm->SetProject(ftd->GetProject(), false);
            }
        }
        cfg->Write(wxT("/find_file_open"), chkOpen->IsChecked());
    }
    else
    {
        // error ?!
        // ... this should not fail (unless the tree was modified during selection)
        // 这不会失败(除非在选择期间修改了树)
    }
}

//响应添加项目虚拟目录
void ProjectManagerUI::OnAddVirtualFolder(cb_unused wxCommandEvent& event) {
    wxString fld = cbGetTextFromUser(wxT("请输入新的虚拟文件夹路径:"), wxT("新建虚拟文件夹"));
    if (fld.IsEmpty()) { return; }

    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk()) { return; }

    FileTreeData *ftd = (FileTreeData *) m_pTree->GetItemData(sel);
    if (!ftd) { return; }

    cbProject *prj = ftd->GetProject();
    if (!prj) { return; }

    ProjectVirtualFolderAdded(prj, m_pTree, sel, fld);
//    RebuildTree();
}

//响应删除虚拟路径
void ProjectManagerUI::OnDeleteVirtualFolder(cb_unused wxCommandEvent& event)
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
    if (!ftd)
        return;

    cbProject* prj = ftd->GetProject();
    if (!prj)
        return;

    ProjectVirtualFolderDeleted(prj, m_pTree, sel);
    RebuildTree();
}

//响应重命名虚拟路径
void ProjectManagerUI::OnRenameVirtualFolder(wxCommandEvent& event)
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
    if (!ftd)
        return;

    cbProject* prj = ftd->GetProject();
    if (!prj)
        return;

    wxString oldName = ftd->GetFolder();

    if (oldName.EndsWith(_T("/")))
        oldName.RemoveLast(1);

    wxTextEntryDialog dlg(Manager::Get()->GetAppWindow(),
                          wxT("请输入虚拟文件夹的新名称:"),
                          wxT("重命名虚拟文件夹"),
                          oldName,
                          wxOK | wxCANCEL | wxCENTRE);
    if (dlg.ShowModal() == wxID_OK)
    {
        ProjectVirtualFolderRenamed(prj, m_pTree, sel, dlg.GetValue());
        RebuildTree();
    }
}

void ProjectManagerUI::OnBeginEditNode(wxTreeEvent& event)
{
    // what item do we start editing?
    // 我们开始编辑什么项?
    wxTreeItemId id = event.GetItem();
    if (!id.IsOk())
    {
        event.Veto();
        return;
    }

    // if no data associated with it, disallow
    // 如果没有与之关联的数据,则不允许
    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(id);
    if (!ftd)
    {
        event.Veto();
        return;
    }

    // only allow editing virtual folders
    // 仅允许编辑虚拟文件夹
    if (ftd->GetKind() != FileTreeData::ftdkVirtualFolder)
        event.Veto();
}

//响应编辑节点结束
void ProjectManagerUI::OnEndEditNode(wxTreeEvent& event)
{
    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(event.GetItem());
    if (!ftd)
    {
        event.Veto();
        return;
    }
    cbProject* prj = ftd->GetProject();
    if (!prj)
    {
        event.Veto();
        return;
    }

    if (!ProjectVirtualFolderRenamed(prj, m_pTree, event.GetItem(), event.GetLabel()))
        event.Veto();
//    RebuildTree();
}

//响应用户界面刷新
void ProjectManagerUI::OnUpdateUI(wxUpdateUIEvent& event)
{
    if (event.GetId() == idMenuFileProperties)
    {
        EditorManager *editorManager = Manager::Get()->GetEditorManager();
        bool enableProperties;
        if (editorManager)
        {
            EditorBase *editor = editorManager->GetActiveEditor();
            EditorBase *startHerePage = editorManager->GetEditor(g_StartHereTitle);

            enableProperties = (editor && editor != startHerePage);
            if (enableProperties)
                enableProperties = !cbHasRunningCompilers(Manager::Get()->GetPluginManager());
        }
        else
            enableProperties = false;

        event.Enable(enableProperties);
    }
    else if (event.GetId() == idMenuProjectProperties || event.GetId() == idMenuAddFile
             || event.GetId() == idMenuAddFilesRecursively || event.GetId() == idMenuRemoveFile
             || event.GetId() == idMenuProjectTreeProps || event.GetId() == idMenuAddVirtualFolder
             || event.GetId() == idMenuDeleteVirtualFolder)
    {
        ProjectManager *projectManager = Manager::Get()->GetProjectManager();
        if (!projectManager || (projectManager->GetIsRunning() != nullptr))
            event.Enable(false);
        else
        {
            cbProject *project = projectManager->GetActiveProject();
            if (!project)
                event.Enable(false);
            else
            {
                bool enable = !cbHasRunningCompilers(Manager::Get()->GetPluginManager());
                event.Enable(enable);
            }
        }
    }
    else
        event.Skip();
}

//响应空闲事件
void ProjectManagerUI::OnIdle(wxIdleEvent& event)
{
    event.Skip();
}

//响应重命名文件
void ProjectManagerUI::OnRenameFile(cb_unused wxCommandEvent& event)
{
    wxTreeItemId sel = GetTreeSelection();
    if (!sel.IsOk())
        return;

    FileTreeData* ftd = (FileTreeData*)m_pTree->GetItemData(sel);
    if (!ftd)
        return;

    cbProject* prj = ftd->GetProject();
    if (!prj)
        return;

    if (ftd->GetProjectFile()->AutoGeneratedBy())
    {
        cbMessageBox(wxT("无法重命名文件,因为它是自动生成的..."), wxT("错误"));
        return;
    }

    wxString path = ftd->GetProjectFile()->file.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
    wxString name = ftd->GetProjectFile()->file.GetFullName();

    wxTextEntryDialog dlg(Manager::Get()->GetAppWindow(), wxT("请输入新名称:"), wxT("重命名文件"), name, wxOK | wxCANCEL | wxCENTRE);
    PlaceWindow(&dlg);
    if (dlg.ShowModal() == wxID_OK)
    {
        wxFileName fn(dlg.GetValue());
        wxString new_name = fn.GetFullName();

        if (name != new_name)
        {
        #ifdef __WXMSW__
            // only overwrite files, if the names are the same, but with different cases
            if (!wxRenameFile(path + name, path + new_name, (name.Lower() == new_name.Lower())))
        #else
            if (!wxRenameFile(path + name, path + new_name, false))
        #endif
            {
                wxBell();
                return;
            }

            if ( ProjectFile* pf = ftd->GetProjectFile() )
            {
                pf->Rename(new_name);
                RebuildTree();
            }
        }
    }
}

//响应按下键
void ProjectManagerUI::OnKeyDown(wxTreeEvent& event)
{
    const wxKeyEvent& key_event = event.GetKeyEvent();

    cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
    if (    prj
        && (prj->GetCurrentlyCompilingTarget() == nullptr)
        && (   key_event.GetKeyCode() == WXK_DELETE
            || key_event.GetKeyCode() == WXK_NUMPAD_DELETE ) )
    {
        // fix delete while drag crash
        // 修复拖动崩溃时的删除
        m_DraggingSelection.Clear();
        wxCommandEvent command(0, idMenuRemoveFilePopup);
        OnRemoveFileFromProject(command);
    }
    else
        event.Skip();
}

//响应向上移动项目项
void ProjectManagerUI::MoveProjectUp(cbProject* project, bool warpAround)
{
    if (!project)
        return;
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    ProjectsArray* pa = pm->GetProjects();

    int idx = pa->Index(project);
    // project not opened in project manager???
    // 项目管理器中没有打开项目???
    if (idx == wxNOT_FOUND)
        return;

    if (idx == 0)
    {
         if (!warpAround)
            return;
        else
            idx = pa->Count();
    }
    pa->RemoveAt(idx--);
    pa->Insert(project, idx);
    RebuildTree();
    if (pm->GetWorkspace())
        pm->GetWorkspace()->SetModified(true);

    // re-select the project
    // 重新选择项目
    wxTreeItemId itemId = project->GetProjectNode();
    cbAssert(itemId.IsOk());
    m_pTree->SelectItem(itemId);
}

//响应向下移动项目项
void ProjectManagerUI::MoveProjectDown(cbProject* project, bool warpAround)
{
    if (!project)
        return;
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    ProjectsArray* pa = pm->GetProjects();

    int idx = pa->Index(project);
    // 项目管理器中没有打开项目???
    if (idx == wxNOT_FOUND)
        return;

    if (idx == (int)pa->Count() - 1)
    {
         if (!warpAround)
            return;
        else
            idx = 0;
    }
    pa->RemoveAt(idx++);
    pa->Insert(project, idx);
    RebuildTree();
    if (pm->GetWorkspace())
        pm->GetWorkspace()->SetModified(true);

    // re-select the project
    wxTreeItemId itemId = project->GetProjectNode();
    cbAssert(itemId.IsOk());
    m_pTree->SelectItem(itemId);
}

//响应询问关闭所有项目
bool ProjectManagerUI::QueryCloseAllProjects()
{
    if (!Manager::Get()->GetEditorManager()->QueryCloseAll())
        return false;
    ProjectsArray* pa = Manager::Get()->GetProjectManager()->GetProjects();
    for (size_t i = 0; i < pa->GetCount(); ++i)
    {
        // Ask for saving modified projects. However,
        // we already asked to save projects' files;
        // do not ask again
        //
        // 要求保存修改过的项目但是.我们已经要求保存项目的文件,不要再询问

        if (!QueryCloseProject((*pa)[i], true))
            return false;
    }
    return true;
}

//响应询问关闭项目
bool ProjectManagerUI::QueryCloseProject(cbProject* proj, bool dontsavefiles)
{
    if (!proj)
        return true;
    if (proj->GetCurrentlyCompilingTarget())
        return false;
    if (!dontsavefiles)
    {
        if (!proj->QueryCloseAllFiles())
            return false;
    }
    if (proj->GetModified() && !Manager::IsBatchBuild())
    {
        wxString msg;
        msg.Printf(wxT("项目'%s'已修改...\n是否保存更改?"), proj->GetTitle().wx_str());
        switch (cbMessageBox(msg, wxT("保存项目"), wxICON_QUESTION | wxYES_NO | wxCANCEL))
        {
            case wxID_YES:
                if (!proj->Save())
                    return false;
            case wxID_NO:
                break;
            case wxID_CANCEL: // fall-through
            default:
                return false;
        }
    }
    return true;
}

//响应询问关闭工作区
bool ProjectManagerUI::QueryCloseWorkspace()
{
    cbWorkspace* wkspc = Manager::Get()->GetProjectManager()->GetWorkspace();
    if (!wkspc)
        return true;

    // Don't ask to save the default workspace, if blank workspace is used on app startup.
    // 如果应用程序启动时使用空白工作区,则不要求保存默认工作区.
    bool blankWorkspace = Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/blank_workspace"), true);
    if ( !(wkspc->IsDefault() && blankWorkspace) )
    {
        // always save workspace layout
        // 始终保存工作区布局
        wkspc->SaveLayout();
        if ( wkspc->GetModified() )
        {
            // workspace needs save
            // 工作区需要保存
            wxString msg;
            msg.Printf(wxT("工作区'%s'已修改. 要保存吗?"), wkspc->GetTitle().wx_str());
            switch (cbMessageBox(msg, wxT("保存工作区"),
                                 wxYES_NO | wxCANCEL | wxICON_QUESTION))
            {
                case wxID_YES:
                    Manager::Get()->GetProjectManager()->SaveWorkspace();
                    break;
                case wxID_CANCEL:
                    return false;
                default:
                    break;
            }
        }
    }

    // We always want to ask to save all loaded projects.
    // 我们总是希望保存所有加载的项目.
    if (!QueryCloseAllProjects())
        return false;
    return true;
}

//响应请求构建目标索引
int ProjectManagerUI::AskForBuildTargetIndex(cbProject* project) {
    cbProject *prj = project;
    if (!prj) { prj = Manager::Get()->GetProjectManager()->GetActiveProject(); }
    if (!prj) { return -1; }

    // ask for target
    // 寻找目标
    wxArrayString array;
    int count = prj->GetBuildTargetsCount();
    for (int i = 0; i < count; ++i) { array.Add(prj->GetBuildTarget(i)->GetTitle()); }
    int target = cbGetSingleChoiceIndex(wxT("选择目标:"), wxT("项目目标"), array, m_pTree, wxSize(300, 400));

    return target;
}

//返回请求多个构建目标索引数组
wxArrayInt ProjectManagerUI::AskForMultiBuildTargetIndex(cbProject* project)
{
    wxArrayInt indices;
    cbProject* prj = project;
    if (!prj)
        prj = Manager::Get()->GetProjectManager()->GetActiveProject();
    if (!prj)
        return indices;

    // ask for target
    // 寻找目标
    wxArrayString array;
    int count = prj->GetBuildTargetsCount();
    for (int i = 0; i < count; ++i)
        array.Add(prj->GetBuildTarget(i)->GetTitle());

    MultiSelectDlg dlg(nullptr, array, true, wxT("选择此文件应属于的目标:"));
    PlaceWindow(&dlg);
    if (dlg.ShowModal() == wxID_OK)
        indices = dlg.GetSelectedIndices();

    return indices;
}

//响应项目配置依赖
void ProjectManagerUI::ConfigureProjectDependencies(cbProject* base)
{
    ProjectDepsDlg dlg(Manager::Get()->GetAppWindow(), base);
    PlaceWindow(&dlg);
    dlg.ShowModal();
}

//检测外部所有修改的项目
void ProjectManagerUI::CheckForExternallyModifiedProjects()
{
    // for some reason, a mutex locker does not work???
    // 由于某种原因,一个互斥锁没有工作???
    if (m_isCheckingForExternallyModifiedProjects)
        return;
    m_isCheckingForExternallyModifiedProjects = true;

    // check also the projects (TO DO : what if we gonna reload while compiling/debugging)
    // TODO : make sure the same project is the active one again
    // 同时检查项目(TODO:如果我们在编译/调试时重新加载会怎么样)
    // 待办事项:确保同一项目再次处于活动状态
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    if ( ProjectsArray* pa = pm->GetProjects())
    {
        bool reloadAll = false;
        // make a copy of all the pointers before we start messing with closing and opening projects
        // the hash (pa) could change the order
        // 在开始处理关闭和打开项目之前,复制所有指针哈希(pa)可能会更改顺序
        std::vector<cbProject*> ProjectPointers;
        for (unsigned int idxProject = 0; idxProject < pa->Count(); ++idxProject)
            ProjectPointers.push_back(pa->Item(idxProject));

        for (unsigned int idxProject = 0; idxProject < ProjectPointers.size(); ++idxProject)
        {
            cbProject* prj = ProjectPointers[idxProject];
            wxFileName fname(prj->GetFilename());
            wxDateTime last = fname.GetModificationTime();
            if (last.IsLaterThan(prj->GetLastModificationTime()))
            {
                // was modified -> reload
                // 已经修改->重新载入
                int ret = -1;
                if (!reloadAll)
                {
                    Manager::Get()->GetLogManager()->Log(prj->GetFilename());
                    wxString msg;
                    msg.Printf(wxT("项目%s在IDE外部被修改...\n是否要重新加载(将丢失所有未保存的工作)?"),
                               prj->GetFilename().wx_str());
                    ConfirmReplaceDlg dlg(Manager::Get()->GetAppWindow(), false, msg);
                    dlg.SetTitle(wxT("重载项目?"));
                    PlaceWindow(&dlg);

                    // Find the window, that actually has the mouse-focus and force a release.
                    // This prevents crash on windows or hang on wxGTK.
                    //
                    // 找到窗口,它实际上有鼠标焦点并强制释放.
                    // 这可以防止在windows上崩溃或挂在wxGTK上.

                    wxWindow* win = wxWindow::GetCapture();
                    if (win)
                        win->ReleaseMouse();

                    ret = dlg.ShowModal();
                    reloadAll = ret == crAll;
                }
                if (reloadAll || ret == crYes)
                    pm->ReloadProject(prj);
                else if (ret == crCancel)
                    break;
                else if (ret == crNo)
                    prj->Touch();
            }
        } // end for : idx : idxProject
    }
    m_isCheckingForExternallyModifiedProjects = false;
}


namespace
{
//项目树排序子级递归
static void ProjectTreeSortChildrenRecursive(cbTreeCtrl* tree, const wxTreeItemId& parent)
{
    if (!tree->ItemHasChildren(parent))
        return;

    tree->SortChildren(parent);

    wxTreeItemIdValue cookie = nullptr;
    wxTreeItemId current = tree->GetFirstChild(parent, cookie);
    while (current)
    {
        ProjectTreeSortChildrenRecursive(tree, current);
        current = tree->GetNextChild(parent, cookie);
    }
}

// helper function used by AddTreeNode
// AddTreeNode使用的助手函数
// 获取相对文件夹路径
static wxString GetRelativeFolderPath(wxTreeCtrl* tree, wxTreeItemId parent)
{
    wxString fld;
    while (parent.IsOk())
    {
        FileTreeData* ftd = (FileTreeData*)tree->GetItemData(parent);
        if (   !ftd
            || (   (ftd->GetKind() != FileTreeData::ftdkFolder)
                && (ftd->GetKind() != FileTreeData::ftdkVirtualFolder) ) )
            break;
        fld.Prepend(tree->GetItemText(parent) + wxFILE_SEP_PATH);
        parent = tree->GetItemParent(parent);
    }
    return fld;
}

//要在其后插入的项目查找节点
wxTreeItemId ProjectFindNodeToInsertAfter(wxTreeCtrl* tree, const wxString& text, const wxTreeItemId& parent, bool in_folders)
{
    wxTreeItemId result;

    if (tree && parent.IsOk())
    {
        wxTreeItemIdValue cookie = nullptr;

        int fldIdx = cbProjectTreeImages::FolderIconIndex();
        int vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();
        wxTreeItemId last;
        wxTreeItemId child = tree->GetFirstChild(parent, cookie);
        while (child)
        {
            bool is_folder = tree->GetItemImage(child) == fldIdx || tree->GetItemImage(child) == vfldIdx;

            if (in_folders)
            {
                if (!is_folder || text.CmpNoCase(tree->GetItemText(child)) < 0)
                {
                    result = last;
                    break;
                }
            }
            else
            {
                if (!is_folder && text.CmpNoCase(tree->GetItemText(child)) < 0)
                {
                    result = last;
                    break;
                }
            }

            last = child;
            child = tree->GetNextChild(parent, cookie);
        }
        if (!result.IsOk())
            result = last;
    }

    return result;
}
//项目添加树节点
wxTreeItemId ProjectAddTreeNode(cbProject* project, wxTreeCtrl* tree,  const wxString& text, const wxTreeItemId& parent,
                                bool useFolders, FileTreeData::FileTreeDataKind folders_kind, bool compiles, int image,
                                FileTreeData* data)
{
    // see if the text contains any path info, e.g. plugins/compilergcc/compilergcc.cpp
    // in that case, take the first element (plugins in this example), create a sub-folder
    // with the same name and recurse with the result...

    // 查看文本是否包含任何路径信息,例如plugins/compilergcc/compilergcc.cpp在这种情况下,
    // 取第一个元素(本例中为plugins),用相同的名称创建一个子文件夹并用结果递归...

    wxTreeItemId ret;

    if (text.IsEmpty())
    {
        delete data;
        return ret;
    }

    wxString path = text;

    // special case for windows and files on a different drive
    // windows和其他驱动器上的文件的特例
    if ( platform::windows && (path.Length() > 1) && (path.GetChar(1) == _T(':')) )
        path.Remove(1, 1);

    // avoid empty node names in case of UNC paths, then, at least the first two chars are slashes
    // 如果是UNC路径,请避免使用空节点名,然后,至少前两个字符是斜杠
    while ((path.Length() > 1) && (path.GetChar(0) == _T('\\') || path.GetChar(0) == _T('/')) )
        path.Remove(0, 1);

    if (path.IsEmpty())
    {
        delete data;
        return ret;
    }

    int pos = path.Find(_T('/'));
    if (pos == -1)
        pos = path.Find(_T('\\'));
    if (useFolders && pos >= 0)
    {
        // ok, we got it. now split it up and re-curse
        // 好的,我们知道了.现在把它分开再递归
        wxString folder = path.Left(pos);
        // avoid consecutive path separators
        // 避免使用连续路径分隔符
        while (path.GetChar(pos + 1) == _T('/') || path.GetChar(pos + 1) == _T('\\'))
            ++pos;
        path = path.Right(path.Length() - pos - 1);

        wxTreeItemIdValue cookie = nullptr;

        wxTreeItemId newparent = tree->GetFirstChild(parent, cookie);
        while (newparent)
        {
            wxString itemText = tree->GetItemText(newparent);
            if (itemText.Matches(folder))
                break;
            newparent = tree->GetNextChild(parent, cookie);
        }

        if (!newparent)
        {
            // in order not to override wxTreeCtrl to sort alphabetically but the
            // folders be always on top, we just search here where to put the new folder...
            //
            // 为了不重写wxTreeCtrl以按字母顺序排序,但文件夹始终位于顶部,我们只在此处搜索将新文件夹放置在何处...
            //
            int fldIdx  = cbProjectTreeImages::FolderIconIndex();
            int vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();

            newparent = ProjectFindNodeToInsertAfter(tree, folder, parent, true);

            FileTreeData* ftd = new FileTreeData(*data);
            ftd->SetKind(folders_kind);
            if (folders_kind != FileTreeData::ftdkVirtualFolder)
                ftd->SetFolder(project->GetCommonTopLevelPath() + GetRelativeFolderPath(tree, parent) + folder + wxFILE_SEP_PATH);
            else
                ftd->SetFolder(GetRelativeFolderPath(tree, parent) + folder + wxFILE_SEP_PATH);
            ftd->SetProjectFile(nullptr);
            int idx = folders_kind != FileTreeData::ftdkVirtualFolder ? fldIdx : vfldIdx;
            newparent = tree->InsertItem(parent, newparent, folder, idx, idx, ftd);
        }
        ret = ProjectAddTreeNode(project, tree, path, newparent, true, folders_kind, compiles, image, data);
    }
    else
    {
        ret = tree->AppendItem(parent, text, image, image, data);
        if (!compiles)
        {
            ColourManager *manager = Manager::Get()->GetColourManager();
            tree->SetItemTextColour(ret, manager->GetColour(wxT("project_tree_non_source_files")));
        }
    }
    return ret;
}

//项目为可拖拉节点
bool ProjectCanDragNode(cbProject* project, wxTreeCtrl* tree, wxTreeItemId node)
{
    // what item do we start dragging?
    // 我们开始拖拉什么项
    if (!node.IsOk())
        return false;

    // if no data associated with it, disallow
    // 如果没有数据关联则不允许
    FileTreeData* ftd = (FileTreeData*)tree->GetItemData(node);
    if (!ftd)
        return false;

    // if not ours, disallow
    // 如果不是我们的,不允许
    if (ftd->GetProject() != project)
        return false;

    // allow only if it is a file or a virtual folder
    // 仅当它是文件或虚拟文件夹时才允许
    return (   (ftd->GetKind() == FileTreeData::ftdkFile)
            || (ftd->GetKind() == FileTreeData::ftdkVirtualFolder) );
}

//递归复制项目树节点
void ProjectCopyTreeNodeRecursively(wxTreeCtrl* tree, const wxTreeItemId& item, const wxTreeItemId& new_parent)
{
    // first, some sanity checks
    // 首先做些完整性检查
    if (!tree || !item.IsOk() || !new_parent.IsOk())
        return;

    FileTreeData* ftd       = (FileTreeData*)tree->GetItemData(item);
    FileTreeData* ftd_moved = ftd ? new FileTreeData(*ftd) : nullptr;
    int           idx       = tree->GetItemImage(item); // old image
    wxColour      col       = tree->GetItemTextColour(item); // old colour

    wxTreeItemId insert = ProjectFindNodeToInsertAfter(tree, tree->GetItemText(item), new_parent, ftd && ftd->GetKind() == FileTreeData::ftdkVirtualFolder);
    wxTreeItemId target = tree->InsertItem(new_parent, insert, tree->GetItemText(item), idx, idx, ftd_moved);
    tree->SetItemTextColour(target, col);

    // recurse for folders
    // 为文件夹递归
    if (tree->ItemHasChildren(item))
    {
        // vfolder: recurse for files all contained files virtual path
        // vfolder:为文件递归所有包含的文件虚拟路径
        wxTreeItemIdValue cookie;
        wxTreeItemId child = tree->GetFirstChild(item, cookie);
        while (child.IsOk())
        {
            ProjectCopyTreeNodeRecursively(tree, child, target);
            child = tree->GetNextChild(item, cookie);
        }
    }

    if (!tree->IsExpanded(new_parent))
        tree->Expand(new_parent);

    if (ftd_moved && ftd_moved->GetProjectFile())
        ftd_moved->GetProjectFile()->virtual_path = GetRelativeFolderPath(tree, new_parent);
}

//已拖动项目虚拟文件夹
bool ProjectVirtualFolderDragged(cbProject* project, wxTreeCtrl* tree, wxTreeItemId from, wxTreeItemId to)
{
    FileTreeData* ftdFrom = static_cast<FileTreeData*>(tree->GetItemData(from));
    FileTreeData* ftdTo   = static_cast<FileTreeData*>(tree->GetItemData(to)  );
    if (!ftdFrom || !ftdTo)
        return false;

    wxString sep = wxString(wxFileName::GetPathSeparator());
    wxChar sepChar = wxFileName::GetPathSeparator();
    wxString fromFolderPath = ftdFrom->GetFolder();
    wxString toFolderPath = ftdTo->GetFolder();

    wxString fromFolder = fromFolderPath;
    fromFolder = fromFolder.RemoveLast();
    fromFolder = fromFolder.AfterLast(sepChar) + sep;
    wxString toFolder = toFolderPath;
    toFolder = toFolder.RemoveLast();
    toFolder = toFolder.AfterLast(sepChar) + sep;

    if (ftdFrom->GetKind() == FileTreeData::ftdkVirtualFolder && ftdTo->GetKind() == FileTreeData::ftdkVirtualFolder)
    {
        const wxArrayString &oldArray = project->GetVirtualFolders();
        wxArrayString newFolders;
        for (size_t i = 0; i < oldArray.GetCount(); ++i)
        {
            wxString item = oldArray[i];
            wxString toFolderStr;
            if (toFolderPath.StartsWith(fromFolderPath.BeforeFirst(sepChar)))
            {
                // The virtual folder is drageed under same root
                // 虚拟文件夹被拖放到同一根目录下
                int posFrom = item.Find(fromFolderPath);
                if (posFrom != wxNOT_FOUND)
                {
                    wxString fromFolderStr = item.Mid(posFrom);
                    item = item.Left(posFrom);
                    if (!item.IsEmpty())
                        newFolders.Add(item);
                }
                else if (item.IsSameAs(toFolderPath))
                {
                    // First add it to folder structure
                    // 首先将其添加到文件夹结构中
                    newFolders.Add(item);
                    wxString fromFolderStr = fromFolderPath;
                    fromFolderStr = fromFolderStr.RemoveLast();
                    fromFolderStr = fromFolderStr.AfterLast(wxFileName::GetPathSeparator());
                    newFolders.Add(item + fromFolderStr + sep);
                }
                else
                    newFolders.Add(item);
            }
            else
            {
                // A virtual folder has been dropped from a different place
                // 虚拟文件夹已从其他位置删除
                if (item.Find(toFolderPath + fromFolder) != wxNOT_FOUND)
                {
                    cbMessageBox(wxT("目标文件夹中存在另一个同名的虚拟文件夹!"),
                                 wxT("错误"), wxOK | wxICON_ERROR, Manager::Get()->GetAppWindow());
                    return false;
                }
                if (item.StartsWith(toFolderPath.BeforeFirst(sepChar)))
                    newFolders.Add(item);
                else if (item.StartsWith(fromFolderPath.BeforeFirst(sepChar)))
                {
                    int pos = item.Find(fromFolder);
                    if (pos == 0)
                    {
                        if (!toFolderPath.IsEmpty())
                            newFolders.Add(toFolderPath + item);
                    }
                    else
                    {
                        wxString temp = item.Left(pos);
                        newFolders.Add(item.Left(pos));
                        if (!toFolderPath.IsEmpty())
                            newFolders.Add(toFolderPath + item.Mid(pos));
                    }
                }
            }
        }

        project->SetVirtualFolders(newFolders);
    }
    else if (ftdFrom->GetKind() == FileTreeData::ftdkVirtualFolder && ftdTo->GetKind() == FileTreeData::ftdkProject)
    {
        const wxArrayString &oldArray = project->GetVirtualFolders();
        wxArrayString newFolders;

        newFolders.Add(fromFolder);

        for (size_t i = 0; i < oldArray.GetCount(); ++i)
        {
            wxString item = oldArray[i];
            if (item.StartsWith(fromFolder))
            {
                // We can't overwrite an existing folder
                // 无法覆盖现有文件夹
                cbMessageBox(wxT("项目树中存在另一个同名的虚拟文件夹!"),
                             wxT("错误"), wxOK | wxICON_ERROR, Manager::Get()->GetAppWindow());
                return false;
            }
            else if (item.StartsWith(fromFolderPath))
            {
                int pos = item.Find(fromFolderPath);
                if (pos != wxNOT_FOUND)
                {
                    item = item.Mid(pos + fromFolderPath.Length());
                    if (item.Length() > 1)
                    {
                        item = item.Prepend(fromFolder);
                        if (newFolders.Index(item) == wxNOT_FOUND)
                            newFolders.Add(item);
                    }
                    else
                        continue;
                }
                else
                    newFolders.Add(item);
            }
            else
                newFolders.Add(item);
        }

        project->SetVirtualFolders(newFolders);
    }
    return true;
}

//拖动项目节点
bool ProjectNodeDragged(cbProject* project, wxTreeCtrl* tree, wxArrayTreeItemIds& fromArray, wxTreeItemId to)
{
    // what items did we drag?
    // 我们拖动了什么项
    if (!to.IsOk())
        return false;

    // if no data associated with it, disallow
    FileTreeData* ftdTo = (FileTreeData*)tree->GetItemData(to);
    if (!ftdTo)
        return false;

    // if not ours, disallow
    if (ftdTo->GetProject() != project)
        return false;

    // allow only if a file or vfolder was dragged on a file, another vfolder or the project itself
    // 仅当文件或vfolder被拖到文件,其他vfolder或项目本身上时才允许
    if (   (ftdTo->GetKind() != FileTreeData::ftdkFile)
        && (ftdTo->GetKind() != FileTreeData::ftdkVirtualFolder)
        && (ftdTo->GetKind() != FileTreeData::ftdkProject) )
    {
        return false;
    }

    wxTreeItemId parentTo = ftdTo->GetKind() == FileTreeData::ftdkFile ? tree->GetItemParent(to) : to;

    // do all the checking for all selected items first (no movement yet, just checking!)
    // 先检查所有选定项目(尚未移动,仅检查!)
    size_t count = fromArray.Count();
    for (size_t i = 0; i < count; i++)
    {
        wxTreeItemId from = fromArray[i];
        if (!from.IsOk())
            return false;

        // if no data associated with it, disallow
        FileTreeData* ftdFrom = (FileTreeData*)tree->GetItemData(from);
        if (!ftdFrom)
            return false;

        // if not ours, disallow
        if (ftdFrom->GetProject() != project)
            return false;

        // allow only if a file or vfolder was dragged on a file, another vfolder or the project itself
        if (   (ftdFrom->GetKind() != FileTreeData::ftdkFile)
            && (ftdFrom->GetKind() != FileTreeData::ftdkVirtualFolder) )
            return false;

        // don't drag under the same parent
        wxTreeItemId parentFrom = ftdFrom->GetKind() == FileTreeData::ftdkFile ? tree->GetItemParent(from) : from;
        if (parentFrom == parentTo)
            return false;

        // A special check for virtual folders.
        // 对虚拟文件夹的特殊检查.
        if (   (ftdFrom->GetKind() == FileTreeData::ftdkVirtualFolder)
            || (ftdTo->GetKind()   == FileTreeData::ftdkVirtualFolder) )
        {
            wxTreeItemId root = tree->GetRootItem();
            wxTreeItemId toParent = tree->GetItemParent(to);
            while (toParent != root)
            {
                if (toParent == from)
                    return false;
                toParent = tree->GetItemParent(toParent);
            }
            if (!ProjectVirtualFolderDragged(project, tree, from, to))
                return false;
        }
    }

    // now that we have successfully done the checking, do the moving
    // 既然我们已经成功地完成了检查
    for (size_t i = 0; i < count; i++)
    {
        wxTreeItemId from = fromArray[i];
        // finally; make the move
        // 最后采取行动
        ProjectCopyTreeNodeRecursively(tree, from, parentTo);
        // remove old node
        // 删除旧节点
        tree->Delete(from);
    }

    project->SetModified(true);

    Manager::Get()->GetProjectManager()->GetUI().RebuildTree();

    return true;
}

//项目具有虚拟文件夹
bool ProjectHasVirtualFolder(const wxString &folderName, const wxArrayString &virtualFolders)
{
    for (size_t i = 0; i < virtualFolders.GetCount(); ++i)
    {
        if (virtualFolders[i].StartsWith(folderName))
        {
            cbMessageBox(wxT("已存在同名的虚拟文件夹."),
                         wxT("错误"), wxICON_WARNING);
            return false;
        }
    }
    return true;
}

//已添加项目虚拟文件夹
bool ProjectVirtualFolderAdded(cbProject* project, wxTreeCtrl* tree,
                               wxTreeItemId parent_node, const wxString& virtual_folder)
{
    wxString foldername = GetRelativeFolderPath(tree, parent_node);
    foldername << virtual_folder;
    foldername.Replace(_T("/"),  wxString(wxFILE_SEP_PATH), true);
    foldername.Replace(_T("\\"), wxString(wxFILE_SEP_PATH), true);
    if (foldername.Last() != wxFILE_SEP_PATH)
        foldername << wxFILE_SEP_PATH;

    const wxArrayString &virtualFolders = project->GetVirtualFolders();
    if (!ProjectHasVirtualFolder(foldername, virtualFolders))
        return false;
    project->AppendUniqueVirtualFolder(foldername);

    FileTreeData* ftd = new FileTreeData(project, FileTreeData::ftdkVirtualFolder);
    ftd->SetProjectFile(nullptr);
    ftd->SetFolder(foldername);

    int vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();

    ProjectAddTreeNode(project, tree, foldername, project->GetProjectNode(), true,
                       FileTreeData::ftdkVirtualFolder, true, vfldIdx, ftd);
    if (!tree->IsExpanded(parent_node))
        tree->Expand(parent_node);

    project->SetModified(true);

//    Manager::Get()->GetLogManager()->DebugLog(F(_T("VirtualFolderAdded: %s: %s"), foldername.c_str(), GetStringFromArray(m_VirtualFolders, _T(";")).c_str()));
    return true;
}

//已删除项目虚拟文件夹
void ProjectVirtualFolderDeleted(cbProject* project, wxTreeCtrl* tree, wxTreeItemId node)
{
    // what item do we start dragging?
    if (!node.IsOk())
        return;

    // if no data associated with it, disallow
    FileTreeData* ftd = (FileTreeData*)tree->GetItemData(node);
    if (!ftd)
        return;

    // if not ours, disallow
    if (ftd->GetProject() != project)
        return;

    wxString foldername        = GetRelativeFolderPath(tree, node);
    wxString parent_foldername = GetRelativeFolderPath(tree, tree->GetItemParent(node));

    project->RemoveVirtualFolders(foldername);
    if (!parent_foldername.IsEmpty())
        project->AppendUniqueVirtualFolder(parent_foldername);
//    Manager::Get()->GetLogManager()->DebugLog(F(_T("VirtualFolderDeleted: %s: %s"), foldername.c_str(), GetStringFromArray(m_VirtualFolders, _T(";")).c_str()));
}

//已重命名项目虚拟文件夹
bool ProjectVirtualFolderRenamed(cbProject* project, wxTreeCtrl* tree, wxTreeItemId node, const wxString& new_name)
{
    if (new_name.IsEmpty())
        return false;

    if (new_name.First(_T(';')) != wxNOT_FOUND ||
        new_name.First(_T('/')) != wxNOT_FOUND ||
        new_name.First(_T('\\')) != wxNOT_FOUND)
    {
        cbMessageBox(wxT("虚拟文件夹名称不能包含以下特殊字符: \";\", \"\\\" 或者 \"/\"."),
                     wxT("错误"), wxICON_WARNING);
        return false;
    }

    // what item are we renaming?
    if (!node.IsOk())
        return false;

    // is it a different name?
    if (tree->GetItemText(node) == new_name)
        return false;

    // if no data associated with it, disallow
    FileTreeData* ftd = (FileTreeData*)tree->GetItemData(node);
    if (!ftd)
        return false;

    // if not ours, disallow
    if (ftd->GetProject() != project)
        return false;

    wxString old_foldername = GetRelativeFolderPath(tree, node);
    wxString new_foldername = GetRelativeFolderPath(tree, tree->GetItemParent(node)) + new_name + wxFILE_SEP_PATH;

    const wxArrayString &virtualFolders = project->GetVirtualFolders();
    if (!ProjectHasVirtualFolder(new_foldername, virtualFolders))
        return false;

    project->ReplaceVirtualFolder(old_foldername, new_foldername);

//    Manager::Get()->GetLogManager()->DebugLog(F(_T("VirtualFolderRenamed: %s to %s: %s"), old_foldername.c_str(), new_foldername.c_str(), GetStringFromArray(m_VirtualFolders, _T(";")).c_str()));
    return true;
}

/**
* Display the project options dialog.
* 返回值:  True if the dialog was closed with "OK", false if closed with "Cancel".
*
* 显示项目选项对话框
* 返回值:如果用"确定"关闭对话框则为True.如果用"取消"关闭对话框则为false.
*/
bool ProjectShowOptions(cbProject* project)
{
    if (!project)
        return false;
    ProjectOptionsDlg dlg(Manager::Get()->GetAppWindow(), project);
    PlaceWindow(&dlg);
    if (dlg.ShowModal() == wxID_OK)
    {
        // update file details
        FilesList &filesList = project->GetFilesList();
        for (FilesList::iterator it = filesList.begin(); it != filesList.end(); ++it)
        {
            if ( ProjectFile* pf = *it )
                pf->UpdateFileDetails();
        }
        CodeBlocksEvent event(cbEVT_PROJECT_OPTIONS_CHANGED);
        event.SetProject(project);
        Manager::Get()->ProcessEvent(event);
        return true;
    }
    return false;
}
} // anonymous namespace

//构建项目树
void ProjectManagerUI::BuildProjectTree(cbProject* project, cbTreeCtrl* tree, const wxTreeItemId& root, int ptvs, FilesGroupsAndMasks* fgam)
{
    if (!tree)
        return;

#ifdef fileload_measuring
    wxStopWatch sw;
#endif
    int  fldIdx    = cbProjectTreeImages::FolderIconIndex();
    int  vfldIdx   = cbProjectTreeImages::VirtualFolderIconIndex();
    bool read_only = (!wxFile::Access(project->GetFilename().c_str(), wxFile::write));
    int  prjIdx    = cbProjectTreeImages::ProjectIconIndex(read_only);

    tree->SetCompareFunction(ptvs);

    // add our project's root item
    // 添加项目的根项
    FileTreeData* ftd = new FileTreeData(project, FileTreeData::ftdkProject);
    project->SetProjectNode(tree->AppendItem(root, project->GetTitle(), prjIdx, prjIdx, ftd));
    wxTreeItemId  others, generated;
    others = generated = project->GetProjectNode();
    // file group nodes (if enabled)
    wxTreeItemId* pGroupNodes = nullptr;

    // create file-type categories nodes (if enabled)
    // 创建文件类型类别节点(如果启用)
    bool do_categorise = ((ptvs&ptvsCategorize) && fgam);
    if (do_categorise)
    {
        // obtain all group nodes available from "file groups and masks"
        // 从"文件组和掩码"获取所有可用的组节点
        pGroupNodes = new wxTreeItemId[fgam->GetGroupsCount()];
        for (unsigned int i = 0; i < fgam->GetGroupsCount(); ++i)
        {
            ftd = new FileTreeData(project, FileTreeData::ftdkVirtualGroup);
            ftd->SetFolder(fgam->GetGroupName(i));
            pGroupNodes[i] = tree->AppendItem(project->GetProjectNode(), fgam->GetGroupName(i), fldIdx, fldIdx, ftd);
        }
        // add a default category "Generated" for all auto-generated file types
        // 为所有自动生成的文件类型添加默认类别"Generated"
        ftd       = new FileTreeData(project, FileTreeData::ftdkVirtualGroup);
        generated = tree->AppendItem(project->GetProjectNode(), wxT("自动生成"), fldIdx, fldIdx, ftd);

        // add a default category "Others" for all non-matching file-types
        // 为所有不匹配的文件类型添加默认类别"其他"
        ftd       = new FileTreeData(project, FileTreeData::ftdkVirtualGroup);
        others    = tree->AppendItem(project->GetProjectNode(), wxT("其他"), fldIdx, fldIdx, ftd);
    }

    // Now add any virtual folders
    // 现在添加任何虚拟文件夹
    const wxArrayString& virtualFolders = project->GetVirtualFolders();
    for (size_t i = 0; i < virtualFolders.GetCount(); ++i)
    {
        ftd = new FileTreeData(project, FileTreeData::ftdkVirtualFolder);
        ftd->SetFolder(virtualFolders[i]);
        ProjectAddTreeNode(project, tree, virtualFolders[i], project->GetProjectNode(), true,
                           FileTreeData::ftdkVirtualFolder, true, vfldIdx, ftd);
    }

    // iterate all project files and add them to the tree
    // 迭代所有项目文件并将其添加到树中
    int count = 0;
    FilesList& fileList = project->GetFilesList();
    for (FilesList::iterator it = fileList.begin(); it != fileList.end(); ++it)
    {
        ProjectFile* pf = *it;
        if (!pf)
        {
            Manager::Get()->GetLogManager()->DebugLogError(wxT("看起来项目的文件列表坏了?!"));
            continue;
        }

        ftd = new FileTreeData(project, FileTreeData::ftdkFile);
        ftd->SetFileIndex(count++);
        ftd->SetProjectFile(pf);
        ftd->SetFolder(pf->file.GetFullPath());

        wxString nodetext = pf->relativeToCommonTopLevelPath;
        FileTreeData::FileTreeDataKind folders_kind = FileTreeData::ftdkFolder;

        // by default, the parent node is the project node (in case of no grouping, no virtual folders)
        // 默认情况下,父节点是项目节点(在没有分组的情况下,没有虚拟文件夹)
        wxTreeItemId parentNode = project->GetProjectNode();

        // now change the parent node for virtual folders and/or if grouping is enabled
        // first check, if the file is under a virtual folder
        //
        // 现在更改虚拟文件夹的父节点和/或如果启用了分组,请首先检查文件是否在虚拟文件夹下
        //
        if (!pf->virtual_path.IsEmpty())
        {
            nodetext       = pf->virtual_path + wxFILE_SEP_PATH + pf->file.GetFullName();
            folders_kind   = FileTreeData::ftdkVirtualFolder;
            wxString slash = pf->virtual_path.Last() == wxFILE_SEP_PATH ? _T("") : wxString(wxFILE_SEP_PATH);
            ftd->SetFolder(pf->virtual_path);

            project->AppendUniqueVirtualFolder(pf->virtual_path + slash);
        }
        // second check, if files grouping is enabled and find the group parent
        // 第二个检查如果启用了文件分组并找到组父级
        else if (do_categorise && pGroupNodes)
        {
            bool found = false;

            // auto-generated files end up all together
            // 自动生成的文件最终都在一起
            if (pf->AutoGeneratedBy())
            {
                parentNode = generated;
                found = true;
            }
            else
            {
                // else try to match a group
                // 否则尝试匹配一个组
                const wxFileName fname(pf->relativeToCommonTopLevelPath);
                const wxString &fnameFullname = fname.GetFullName();

                for (unsigned int i = 0; i < fgam->GetGroupsCount(); ++i)
                {
                    if (fgam->MatchesMask(fnameFullname, i))
                    {
                        parentNode = pGroupNodes[i];
                        found = true;
                        break;
                    }
                }
            }

            // if not matched a group, put it in "Others" group
            // 如果不匹配一个组,则将其放入"其他"组
            if (!found)
                parentNode = others;
        }

        // probably remove the path of the entry (depending on settings)
        // 可能删除条目的路径(取决于设置)
        if (  !(ptvs&ptvsUseFolders)
            && (ptvs&ptvsHideFolderName)
            && (folders_kind != FileTreeData::ftdkVirtualFolder) )
        {
            nodetext = pf->file.GetFullName();
        }

        // add file in the tree
        // 在树中添加文件
        bool useFolders = (ptvs&ptvsUseFolders) || (folders_kind == FileTreeData::ftdkVirtualFolder);
        pf->SetTreeItemId(ProjectAddTreeNode(project, tree, nodetext, parentNode, useFolders, folders_kind,
                                             pf->compile, (int)pf->GetFileState(), ftd));
    }
    // iteration of project files
    // 项目文件的迭代

    // finally remove empty tree nodes (like empty groups)
    // 最后删除空树节点(如空组)
    if (do_categorise && pGroupNodes)
    {
        for (unsigned int i = 0; i < fgam->GetGroupsCount(); ++i)
        {
            if (tree->GetChildrenCount(pGroupNodes[i], false) == 0)
                tree->Delete(pGroupNodes[i]);
        }
        if (tree->GetChildrenCount(others, false) == 0)
            tree->Delete(others);
        if (tree->GetChildrenCount(generated, false) == 0)
            tree->Delete(generated);
    }
    delete[] pGroupNodes;

    ProjectTreeSortChildrenRecursive(tree, project->GetProjectNode());
    tree->Expand(project->GetProjectNode());
#ifdef fileload_measuring
    Manager::Get()->GetLogManager()->DebugLogError(F(_T("%s::%s:%d  took : %d ms"), cbC2U(__FILE__).c_str(),cbC2U(__PRETTY_FUNCTION__).c_str(), __LINE__, (int)sw.Time()));
#endif
}
